/* app-inbox — copia del sistema de diseño de app-tradeinsight (C9: reuso por copia). No se edita aquí: lo propio va en inbox.css */
/* DM Sans servida por el propio servicio (DS12, 2026-10-02): sin pedirla a Google Fonts. Variable (opsz, wght), de @fontsource-variable/dm-sans 5.3.0, licencia OFL en ../fonts/dm-sans/LICENSE */
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 300 800;
  src: url('../fonts/dm-sans/dm-sans-latin-ext-opsz-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF;
}
@font-face {
  font-family: 'DM Sans';
  font-style: normal;
  font-display: swap;
  font-weight: 300 800;
  src: url('../fonts/dm-sans/dm-sans-latin-opsz-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD;
}

:root {
  /* LO QUE PINTA EL NAVEGADOR Y NO PINTAMOS NOSOTROS (2026-08-28). `color-scheme`
     es lo que hace que los controles NATIVOS -- el desplegable de un <select>, el
     calendario de un <input type=date>, la barra de desplazamiento, el relleno
     automatico del navegador -- se dibujen en el tema correcto. Sin esto, el modo
     oscuro se acaba en el borde de cada control: la lista de un desplegable salia
     blanca sobre la aplicacion oscura, y el autorrelleno pintaba fondo claro con
     nuestro texto claro encima. No se puede arreglar con clases porque ese
     dibujo no es nuestro. */
  color-scheme: light;
  /* Paleta verificada contra WCAG AA (2026-08-04). Los valores anteriores eran
     más vivos pero no pasaban el 4,5:1 exigido para texto: el verde del P&L
     estaba en 3,30:1 sobre blanco y 2,97:1 sobre fila en hover -- justo el dato
     más importante del producto era el peor de leer. Ratios de los actuales
     sobre blanco / --bg / --bg3(hover de fila):
       --a   5,74 / 5,41 / 5,17  ·  --ok 5,43 / 5,11 / 4,89  ·  --danger 5,74 / 5,40 / 5,16
       --warn 4,92 ·  --info 5,50  ·  --dim 5,97 / 5,61 / 5,37
     Si tocas un color, recalcula el ratio antes de darlo por bueno. */
  /* Acento de marca: COBRE (2026-08-05, elección del usuario sobre 9 direcciones).
     Se eligió cobre porque el verde y el rojo ya están tomados por la semántica
     del P&L: un acento en esa familia hace dudar al ojo ante cada cifra.
     La propuesta original era #C0522A, pero daba 4,69/4,41/4,22 -- por debajo del
     4,5:1 en --bg y --bg3. Oscurecido a #AB4722: mismo color a la vista, y de paso
     MEJORA al azul que sustituye (5,17/4,86/4,65). Al bajar de luminosidad se
     separa además del rojo de pérdida --neg (#C81E1E), que es más claro y frío. */
  --a:  #AB4722;   /* 5,74 / 5,41 / 5,17 */
  --a2: #8F3A1B;   /* 7,53 / 7,09 / 6,78 */
  --a-soft: #FDF1EC;  /* fondo del item activo; --a2 encima da 6,80 */
  --bg:    #F7F8FA;
  --bg2:   #FFFFFF;
  --bg3:   #F1F3F7;
  --tx:    #1E2536;
  --dim:   #5B6474;
  --dim2:  #6B7484;
  --bd:    #E4E7EC;
  --bd2:   #D3D8E0;
  --danger: #C81E1E;
  --ok:     #137A38;
  --warn:   #A16207;
  --info:   #6355D8;
  --input-bg:       #FFFFFF;
  --input-bg-focus: #FAFBFF;
  --topbar-bg:      #FFFFFF;
  --modal-bg:       #FFFFFF;
  --toast-bg:       #FFFFFF;
  --shadow:         rgba(30,37,54,.05);
  --scroll-c:       #C9CFD9;
  /* Texto SOBRE un fondo de acento solido (una insignia, un boton primario).
     NO es blanco en los dos temas, y ese es justo el motivo de que exista: en
     oscuro el acento se aclara a #D9754A y el blanco encima da 3,18:1 -- por
     debajo del minimo. Medido, no supuesto (tools/contrast.js). */
  --on-accent: #FFFFFF;
  /* El negro de detras de una imagen (una captura de pantalla). NO es un color
     de tema: es el letterbox de una foto, y en claro tambien va negro -- si
     siguiera al tema, la captura cambiaria de marco y pareceria otra imagen. */
  --media-bg: #000000;

  /* Tokens de DATO (2026-08-04). Antes cada pantalla repetía a mano el verde y
     el rojo del PnL (~150 hex sueltos, con 3 verdes y 2 rojos distintos según
     el fichero: el mismo dato se veía de un color en la tabla y de otro en el
     gráfico). Ahora hay un único par, y las clases .pos/.neg de abajo lo
     aplican. `--c1..--c6` es la paleta de SERIES para gráficos, por el mismo
     motivo: había 12 azules/verdes ad hoc repartidos por los charts. */
  /* ── LAS FAMILIAS SUAVES, TOKENIZADAS (2026-08-27) ────────────────────────
     Eran 52 hex sueltos repartidos por badges, chips, nodos del flujo y capas de
     la pantalla de Arquitectura -- el mismo mal que ya se curo con el par del
     P&L, y con la misma consecuencia: al llegar el tema oscuro, todo eso se
     habria quedado en claro sobre fondo oscuro, ilegible y sin que ninguna
     redefinicion de token pudiera arreglarlo.
     Cada familia son TRES piezas porque asi se usan: fondo, borde y texto. */
  --ok-soft: #EAF7EE;     --ok-bd: #C4E7CF;     --ok-tx: #1B7A3D;
  --warn-soft: #FDF4E6;   --warn-bd: #F3DCB4;   --warn-tx: #97650F;
  --danger-soft: #FDECEC; --danger-bd: #F5C9C9; --danger-tx: #B32020;
  --info-soft: #F1EFFE;   --info-bd: #D9D3FA;   --info-tx: #5B4BC4;
  /* La franja alterna de las tablas: casi blanca, pero en oscuro tiene que ser
     casi negra o la tabla se vuelve un tablero de ajedrez. */
  --zebra: #FCFDFE;
  --pos: #137A38; --pos-soft: #E7F5EC;
  --neg: #C81E1E; --neg-soft: #FCEBEB;
  /* Series de gráfico: son elementos GRÁFICOS, así que el requisito es 3:1 y no
     el 4,5:1 del texto -- por eso pueden ser más vivas que --warn/--info y se
     mantienen como escala aparte. Ratios sobre blanco: 5,17 / 5,02 / 3,19 /
     5,70 / 4,95 / 4,60. Están pensadas para distinguirse también entre sí. */
  --c1: #2563EB; --c2: #15803D; --c3: #D97706; --c4: #7C3AED; --c5: #0E7C86; --c6: #DB2777;

  /* Dos niveles de elevación: las tarjetas en reposo casi no despegan del fondo
     (--sh-1) y lo que flota o responde al ratón sí (--sh-2). Con un único nivel
     plano el panel se leía como un wireframe. */
  --sh-1: 0 1px 2px rgba(30,37,54,.06);
  --sh-2: 0 4px 12px rgba(30,37,54,.09);

  /* --fs-display es para la cifra principal de un KPI: las cifras grandes son
     lo que hace que un panel parezca de producto y no de hoja de cálculo. */
  /* El mínimo eran 10px (--fs-xs, en ~87 sitios) y 11px (badges, botones,
     labels): por debajo del mínimo legible aceptable en un producto de pago.
     xs y sm han convergido en 12px a propósito -- eran casi iguales y tener dos
     tokens indistinguibles solo invitaba a elegir al azar. */
  --fs-xs: 12px; --fs-sm: 12px; --fs-nm: 12px; --fs-base: 13px; --fs-md: 14px; --fs-lg: 15px;
  --fs-display: 30px;
  /* Ancho maximo del contenido de una pantalla. Sin el, en un monitor de 2.560px
     una tabla se estira a 2.400 y el ojo recorre medio metro entre el nombre de
     la fila y su cifra. 1600 es generoso a proposito: en un portatil de 1.280 no
     se nota siquiera, y solo empieza a actuar donde de verdad sobra sitio. */
  --content-max: 1600px;
  --nav-h: 48px; --side-w: 240px; --side-w-collapsed: 60px; --radius: 8px; --radius-lg: 10px;
}

/* ── TEMA OSCURO (2026-08-27) ────────────────────────────────────────────────
   No habia NI UNA regla de esquema de color en 2.386 lineas. Para cualquier
   producto seria una carencia; para este es una anomalia competitiva: se vende
   por suscripcion anual a gente que mira graficos entre dos y diez horas al dia,
   y TradingView, MetaTrader, TradeZella y Tradervue son todos oscuros o lo
   ofrecen.

   ── TRES ESTADOS, NO DOS ──────────────────────────────────────────────────
   El usuario puede estar en claro, en oscuro, o --el defecto-- SIGUIENDO AL
   SISTEMA. Los tres se resuelven con esta estructura, y el orden importa:

     :root                       la paleta clara COMPLETA. Es la base: todo token
                                 existe aqui, asi que ninguna regla puede quedarse
                                 sin valor.
     @media (prefers-color-scheme: dark) :root:not([data-theme="light"])
                                 el sistema manda... salvo que el usuario haya
                                 elegido claro explicitamente. Sin ese :not, un
                                 usuario en claro con el sistema en oscuro veria
                                 la aplicacion oscura y su eleccion no serviria.
     :root[data-theme="dark"]    la eleccion explicita gana en la otra direccion.

   ── LOS COLORES NO SE INVIERTEN, SE RECALCULAN ────────────────────────────
   La paleta clara lleva sus ratios medidos desde 2026-08-04 y bajar ese liston
   aqui seria tirar lo mejor que tiene la interfaz. Lo comprueba
   `tools/contrast.js` desde `test/contrast.test.js`, que calcula los ratios en
   vez de creerselos.

   Dos casos donde el color clara NO vale y hay que decirlo:
     - El COBRE de marca (#AB4722) da 3,1:1 sobre el fondo oscuro y no llega.
       Se aclara a #D9754A, que sigue siendo el mismo color a la vista.
     - El verde y el rojo del P&L se aclaran por lo mismo. Siguen siendo verde y
       rojo porque esa convencion en una plataforma financiera no se toca.

   Los fondos NO son grises puros: llevan una pizca de azul, que es lo que hace
   que un panel oscuro se lea como elegido y no como "negro por defecto". */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --a: #D9754A; --a2: #E8926C; --a-soft: #2A1C15;
    --bg: #14171F; --bg2: #1B1F29; --bg3: #232833;
    --tx: #E8EBF0; --dim: #A2ABBB; --dim2: #98A1B1;
    --bd: #2E3440; --bd2: #3C4351;
    --danger: #F87171; --ok: #4ADE80; --warn: #FBBF24; --info: #A5B4FC;
    /* Sobre el acento aclarado del tema oscuro, el que se lee es el texto
       oscuro (4,80:1); el blanco se queda en 3,18:1. */
    --on-accent: #1E2536; --media-bg: #000000;
    --input-bg: #232833; --input-bg-focus: #272D3A;
    --topbar-bg: #1B1F29; --modal-bg: #1B1F29; --toast-bg: #232833;
    --shadow: rgba(0,0,0,.4); --scroll-c: #3C4351;
    --pos: #4ADE80; --pos-soft: #16281C;
    --neg: #F87171; --neg-soft: #2C1719;
    /* Las series de grafico: mismo orden y misma intencion, aclaradas para que
       se distingan sobre fondo oscuro. Son elementos GRAFICOS (3:1), no texto. */
    --c1: #60A5FA; --c2: #4ADE80; --c3: #FBBF24; --c4: #C4B5FD; --c5: #5EEAD4; --c6: #F9A8D4;
    /* Las familias suaves: en claro son casi blancas y aqui casi negras, con el
       tinte de su significado. Invertirlas daria fondos chillones. */
    --ok-soft: #16281C;     --ok-bd: #245C36;     --ok-tx: #6EE7A0;
    --warn-soft: #2A2113;   --warn-bd: #6B5220;   --warn-tx: #FCD34D;
    --danger-soft: #2C1719; --danger-bd: #6E2A2C; --danger-tx: #FCA5A5;
    --info-soft: #1E1E33;   --info-bd: #3F3F72;   --info-tx: #C7CFFF;
    --zebra: #1E222C;
    --sh-1: 0 1px 2px rgba(0,0,0,.35);
    --sh-2: 0 4px 12px rgba(0,0,0,.5);
  }
}
:root[data-theme="dark"] {
    color-scheme: dark;
    --a: #D9754A; --a2: #E8926C; --a-soft: #2A1C15;
    --bg: #14171F; --bg2: #1B1F29; --bg3: #232833;
    --tx: #E8EBF0; --dim: #A2ABBB; --dim2: #98A1B1;
    --bd: #2E3440; --bd2: #3C4351;
    --danger: #F87171; --ok: #4ADE80; --warn: #FBBF24; --info: #A5B4FC;
    /* Sobre el acento aclarado del tema oscuro, el que se lee es el texto
       oscuro (4,80:1); el blanco se queda en 3,18:1. */
    --on-accent: #1E2536; --media-bg: #000000;
    --input-bg: #232833; --input-bg-focus: #272D3A;
    --topbar-bg: #1B1F29; --modal-bg: #1B1F29; --toast-bg: #232833;
    --shadow: rgba(0,0,0,.4); --scroll-c: #3C4351;
    --pos: #4ADE80; --pos-soft: #16281C;
    --neg: #F87171; --neg-soft: #2C1719;
    /* Las series de grafico: mismo orden y misma intencion, aclaradas para que
       se distingan sobre fondo oscuro. Son elementos GRAFICOS (3:1), no texto. */
    --c1: #60A5FA; --c2: #4ADE80; --c3: #FBBF24; --c4: #C4B5FD; --c5: #5EEAD4; --c6: #F9A8D4;
    /* Las familias suaves: en claro son casi blancas y aqui casi negras, con el
       tinte de su significado. Invertirlas daria fondos chillones. */
    --ok-soft: #16281C;     --ok-bd: #245C36;     --ok-tx: #6EE7A0;
    --warn-soft: #2A2113;   --warn-bd: #6B5220;   --warn-tx: #FCD34D;
    --danger-soft: #2C1719; --danger-bd: #6E2A2C; --danger-tx: #FCA5A5;
    --info-soft: #1E1E33;   --info-bd: #3F3F72;   --info-tx: #C7CFFF;
    --zebra: #1E222C;
    --sh-1: 0 1px 2px rgba(0,0,0,.35);
    --sh-2: 0 4px 12px rgba(0,0,0,.5);
}


*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* ── LOS PUNTOS DE CORTE: este es el conjunto COMPLETO ─────────────────────
   Habia diez anchos distintos (1250 1100 1001 1000 900 760 720 700 640 600), sin
   nombre y varios separados por veinte pixeles. El coste no era estetico: quien
   anadia una pantalla tenia que ADIVINAR cual usar, y acababa inventando el
   undecimo. Es la misma enfermedad que ya se curo con los tokens de color, cuando
   habia 150 hex sueltos y tres verdes distintos para el mismo dato.

   ⚠️ NO SE PUEDEN PONER EN UNA VARIABLE. `@media (max-width: var(--bp-tablet))` no
   funciona -- las custom properties no se resuelven en la condicion de una media
   query -- y aqui no hay paso de compilacion ni lo va a haber. Asi que el conjunto
   se declara AQUI y lo vigila `tools/breakpoints.js` desde `test/breakpoints.test.js`:
   un ancho que no este en esta lista hace fallar la suite.

   ── Los CUATRO de maquetacion ───────────────────────────────────────────────
     760   movil <-> no movil. Aqui el menu se hace cajon, las tablas se vuelven
           tarjetas y el paginador se apila. No se mueve a 768: desplazaria la
           frontera de tarjeta/cajon de seis bloques para ganar ocho pixeles.
     761   el par sin solape de 760, o sea el limite INFERIOR del rango de tablet.
           Va junto a su pareja igual que 1000/1001.
     900   estrecho. Patas de operacion, pasos de bienvenida, consola de TradingView.
    1100   medio. Acciones de la miga y el cuadro de mando a dos columnas.
           (grid-6 salio de aqui el 2026-08-27: ya se reacomoda sola.)
    1180   tablet <-> escritorio. Es el corte del RAIL: por debajo, el menu lateral
           se dibuja como iconos de 60px. Un iPad en horizontal son 1180 y sigue
           siendo tactil, asi que entra.

   ── Los TRES justificados por contenido ─────────────────────────────────────
   Se quedan porque hay algo que deja de caber EXACTAMENTE ahi, y cada uno lo dice.
   Es lo que los distingue de un numero inventado.
     700   el nombre de la maquina deja de caber en el mapa de disponibilidad.
    1000   las tres columnas de resumen de Operativa (con su par min-width:1001,
           que es la forma correcta de no solapar).
    1250   los dos graficos de patrimonio dejan de caber uno al lado del otro.

   Anadir uno nuevo = anadirlo aqui CON SU MOTIVO. Si el motivo es "me hacia
   falta un numero por ahi", el que hace falta es uno de los cuatro de arriba. */

html, body {
  height: 100%; font-family: 'DM Sans', system-ui, sans-serif; font-size: var(--fs-base); color: var(--tx);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}
a { color: var(--a); text-decoration: none; }
button { cursor: pointer; font-family: inherit; font-size: inherit; }
input, select, textarea { font-family: inherit; font-size: inherit; color: var(--tx); }
/* EL SUELO DE TODO CONTROL DE TEXTO (2026-08-28). La linea de arriba daba el
   COLOR DE LETRA y dejaba el fondo al navegador, que lo pinta BLANCO. En claro no
   se notaba; en oscuro, el texto casi blanco sobre ese blanco dejaba las cajas
   ilegibles -- se escribia y no se veia nada. Paso en el maestro de pares y en
   cualquier <input> que no viva dentro de un `.field`, que son unos cuantos.
   Un fondo declarado y una letra declarada van SIEMPRE juntos: media pareja es
   como se consigue exactamente este fallo.

   `:where()` vale CERO en especificidad, y esa es la razon de usarlo: es un suelo
   que cualquier regla existente (`.field input`, `.pager-size`, `.tvc-bar input`)
   sigue pisando sin tocarla. Fuera quedan las casillas y los controles que el
   navegador dibuja el solo: darles fondo y borde los rompe. */
:where(input:not([type=checkbox]):not([type=radio]):not([type=range]):not([type=color]):not([type=file])),
:where(select),
:where(textarea) {
  background: var(--input-bg); border: 1px solid var(--bd2);
  border-radius: var(--radius); padding: 4px 8px;
}
:where(input, textarea):focus { background: var(--input-bg-focus); }
/* El texto de ayuda de dentro de la caja. `opacity: 1` porque Firefox le mete un
   0,54 propio: sin eso, el gris ya tenue de --dim2 se queda en un fantasma. */
::placeholder { color: var(--dim2); opacity: 1; }
/* Las casillas y los radios se pintan con el color de marca en vez de con el azul
   del sistema, que es de otra aplicacion. */
input[type=checkbox], input[type=radio] { accent-color: var(--a); }

.topbar {
  position: fixed; top: 0; left: 0; right: 0; height: var(--nav-h);
  background: var(--topbar-bg);
  border-bottom: 1px solid var(--bd);
  display: flex; align-items: center; gap: 10px; padding: 0 16px; z-index: 100;
}
.menu-toggle { display: none; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; background: none; color: var(--tx); border-radius: 6px; }
.menu-toggle:hover { background: var(--bg3); }
/* Iconos de la barra superior: mismo trazo, grosor y color que los del menú
   lateral (ver .sidebar-icon svg). Eran emoji y desentonaban -- los pinta la
   fuente de emoji del sistema, en color y con otro peso. */
.topbar-icon {
  width: 22px; height: 22px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
/* El logo es un botón: lleva al cuadro de mando (2026-08-04, petición del
   usuario -- "pinchar en el logo debe recargar la home"). */
.topbar-logo {
  display: flex; align-items: center; gap: 8px;
  border: none; background: none; padding: 4px 8px 4px 6px; border-radius: 6px;
  font-size: var(--fs-md); font-weight: 700; letter-spacing: -.2px; color: var(--tx);
}
.topbar-logo:hover { background: var(--bg3); }
/* La marca hereda currentColor en sus barras neutras (ver el SVG en index.html). */
.topbar-mark { width: 19px; height: 19px; flex: none; display: block; }
/* OJO: el acento va en el `span` INTERIOR del wordmark. `.topbar-logo span` a
   secas también atraparía al propio .topbar-wordmark (que es un span) y pintaría
   "TradeInsight" entero de acento. */
.topbar-logo .topbar-wordmark span { color: var(--a); }
.topbar-spacer { flex: 1; }
/* ── El acceso VISIBLE a la paleta de comandos (2026-08-27) ─────────────────
   Ctrl+K no se descubre solo, y en un movil no hay Ctrl. Se pinta como un campo
   de busqueda --que es lo que la gente busca con la vista-- aunque sea un boton:
   al pulsarlo se abre la paleta, que ya trae su propio cuadro de texto. Un input
   de verdad aqui obligaria a mantener dos sitios donde se escribe lo mismo. */
.topbar-search {
  display: inline-flex; align-items: center; gap: 8px; height: 32px; padding: 0 10px;
  border: 1px solid var(--bd2); border-radius: var(--radius); background: var(--bg);
  color: var(--dim); font-family: inherit; font-size: var(--fs-sm); cursor: pointer;
  min-width: 190px;
}
.topbar-search:hover { border-color: var(--a); color: var(--tx); }
.topbar-search-txt { flex: 1; text-align: left; }
.topbar-search-kbd {
  flex: none; padding: 1px 6px; border: 1px solid var(--bd2); border-radius: 4px;
  background: var(--bg2); font-family: inherit; font-size: 10px; color: var(--dim2);
}
.topbar-tabs { display: flex; gap: 2px; }
.topbar-tab { height: 30px; padding: 0 14px; border-radius: 8px; border: none; background: none; font-size: var(--fs-sm); font-weight: 500; color: var(--dim); transition: background .15s, color .15s; }
.topbar-tab:hover { background: var(--bg3); color: var(--tx); }
.topbar-tab.active { background: var(--bg3); color: var(--tx); font-weight: 600; }

/* Campana de notificaciones/tareas: el badge lleva el nº de pendientes (avisos
   sin archivar + tareas sin completar). Se oculta solo cuando no hay nada. */
.bell-btn { position: relative; display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; background: none; color: var(--tx); border-radius: 50%; }
.bell-btn:hover { background: var(--bg3); }
.bell-badge {
  position: absolute; top: -2px; right: -2px; min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--danger); color: #fff; font-size: 10px; font-weight: 700;
  line-height: 16px; text-align: center;
}
.bell-badge[hidden] { display: none; }

.user-menu { position: relative; }
.user-menu-btn { display: flex; align-items: center; justify-content: center; width: 32px; height: 32px; border: none; background: none; color: var(--tx); border-radius: 50%; }
.user-menu-btn:hover { background: var(--bg3); }
.user-menu-panel {
  display: none; position: absolute; top: calc(100% + 8px); right: 0; width: 240px;
  background: var(--modal-bg); border: 1px solid var(--bd); border-radius: var(--radius);
  box-shadow: 0 12px 32px var(--shadow); padding: 14px; flex-direction: column; gap: 12px; z-index: 150;
}
.user-menu-panel.open { display: flex; }
/* Bloques de "este dispositivo" (instalación y avisos push) dentro del menú de
   usuario: son ajustes del móvil/ordenador, no de la cuenta. */
.device-block { border-top: 1px solid var(--bd); padding-top: 10px; }
.device-title { font-size: var(--fs-sm); font-weight: 600; margin-bottom: 6px; }
.device-block .hint { font-size: var(--fs-xs); line-height: 1.5; }
.device-block ol { font-size: var(--fs-xs); color: var(--dim); }
.pwa-steps { margin: 6px 0 0 18px; padding: 0; line-height: 1.6; }
/* Diagnóstico de instalación: una línea por condición, con su ✓/✗ y el motivo. */
.pwa-diag { list-style: none; margin: 8px 0 0; padding: 0; display: flex; flex-direction: column; gap: 6px; }
.pwa-diag li { font-size: var(--fs-xs); }
.pwa-diag .pwa-ok > strong, .pwa-diag li.pwa-ok strong { color: var(--pos); }
.pwa-diag li.pwa-bad strong { color: var(--neg); }
.pwa-diag .hint { margin-left: 14px; }

.layout { display: flex; height: 100vh; padding-top: var(--nav-h); }
/* Columna flex: la lista de items ocupa el alto disponible y se lleva el scroll,
   de modo que el botón de colapsar queda anclado al PIE del menú (2026-08-04,
   petición del usuario) aunque la lista de pestañas crezca. */
.sidebar {
  width: var(--side-w); flex-shrink: 0; background: var(--bg2);
  border-right: 1px solid var(--bd); overflow: hidden;
  display: flex; flex-direction: column; padding: 10px 8px 8px; transition: width .18s ease;
}
.sidebar-items { flex: 1; min-height: 0; overflow-y: auto; overflow-x: hidden; }
.sidebar-item { display: flex; align-items: center; gap: 11px; padding: 9px 12px; border-radius: var(--radius); cursor: pointer; font-size: var(--fs-md); color: var(--tx); white-space: nowrap; }
.sidebar-item:hover { background: var(--bg3); }
.sidebar-badge {
  margin-left: auto; min-width: 18px; height: 18px; padding: 0 5px; border-radius: 9px;
  background: var(--a); color: #fff; font-size: 10px; font-weight: 700; line-height: 18px; text-align: center;
}
.sidebar-badge[hidden] { display: none; }
.sidebar-item.active { background: var(--a-soft); color: var(--a2); font-weight: 600; }

/* ── Menú de dos niveles (2026-08-08) ────────────────────────────────────────
   El padre y el hijo se marcan A LA VEZ pero con tratamientos DISTINTOS, que es
   lo que permite leer los dos de un vistazo:

     · hijo activo  -> relleno sólido (--a-soft) + acento + negrita. Es LA
                       pantalla abierta, así que se lleva el peso visual.
     · padre en ruta-> sin relleno: solo la barra lateral de acento y el texto en
                       --a2. Es el ÁREA, no la pantalla.

   Con el mismo estilo en los dos, el ojo no distingue cuál de las dos líneas es
   la pantalla en la que estás -- que es justo lo que hay que poder distinguir.
   El nivel 2 se sangra y su icono es MÁS PEQUEÑO (18px frente a 22px), para que
   la jerarquía se vea aunque no haya ningún grupo desplegado. */
.nav-group + .nav-group { margin-top: 2px; }
.nav-parent {
  position: relative;
  font-weight: 600; color: var(--dim);
  text-transform: uppercase; font-size: var(--fs-xs); letter-spacing: .04em;
}
.nav-parent .sidebar-label { flex: 1; }
/* La barra lateral marca el área activa sin competir con el relleno del hijo. */
.nav-parent.in-path::before {
  content: ''; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 3px; border-radius: 2px; background: var(--a);
}
.nav-parent.in-path { color: var(--a2); }
.nav-caret {
  margin-left: auto; flex-shrink: 0; color: var(--dim2); font-size: 10px;
  transition: transform .16s ease;
}
.nav-group:not(.open) .nav-caret { transform: rotate(-90deg); }
.nav-children { display: none; }
.nav-group.open .nav-children { display: block; }
/* Sangría + guía vertical: dice "esto cuelga de lo de arriba" sin más adorno. */
.nav-children { margin: 2px 0 4px 18px; padding-left: 10px; border-left: 1px solid var(--bd); }
.nav-child { padding: 7px 10px; font-size: var(--fs-sm); color: var(--dim); gap: 9px; }
.nav-child .sidebar-icon svg { width: 18px; height: 18px; }
.nav-child:hover { color: var(--tx); }
.nav-child.active { color: var(--a2); }
/* La hoja que CONTIENE la subpantalla abierta: acento pero sin el peso de la
   activa. Con los dos iguales no se distingue cuál de las dos líneas es la
   pantalla que se está viendo (misma razón que .nav-parent.in-path). */
.nav-child.in-path { color: var(--a2); opacity: .85; }

/* TERCER NIVEL (2026-08-27). Solo se despliega el de la hoja en la que estás:
   los de todas las hojas a la vez devolverían el menú de once entradas de golpe
   que el acordeón vino a quitar. Sin icono a propósito -- el nivel se lee por
   sangrado, y un tercer juego de símbolos compite con los dos de arriba. */
.nav-grandchildren { display: none; margin: 0 0 4px 16px; padding-left: 10px; border-left: 1px solid var(--bd); }
.nav-grandchildren.open { display: block; }
.nav-grandchild { padding: 5px 10px; font-size: var(--fs-xs); color: var(--dim); gap: 8px; }
.nav-grandchild:hover { color: var(--tx); }
.nav-grandchild.active { color: var(--a2); font-weight: 600; }
/* TRES NIVELES, TRES TAMAÑOS (2026-08-27, petición del usuario). El tamaño es lo
   que dice la jerarquía sin una sola línea más de sangría: 22 px el área, 18 el
   de la sección y 15 la subpantalla. Todos llevan icono --antes el tercer nivel
   iba sin él y se leía solo por el sangrado, que en una lista de once entradas no
   basta para saber dónde está uno. */
.nav-grandchild .sidebar-icon svg { width: 15px; height: 15px; stroke-width: 1.7; }
/* En compacto el tercer nivel no cabe: la subpantalla se alcanza por el flyout del
   grupo (y por la portada). Un icono de 15 px centrado en una columna de 24 se
   leería como un adorno, no como una entrada. */
.sidebar:is(.collapsed, .rail-auto) .nav-grandchildren { display: none; }
/* El mismo nivel dentro del flyout, donde sí hay sitio para el texto. */
.flyout-item.flyout-sub { padding-left: 34px; font-size: var(--fs-xs); }

/* Iconos de trazo monocromo (SVG inline en index.html). El trazo se declara
   aquí y no en cada SVG para no repetirlo 10 veces; `currentColor` hace que
   sigan el color del item (gris oscuro / azul si está activo) sin más reglas. */
.sidebar-icon { flex-shrink: 0; display: flex; align-items: center; justify-content: center; }
.sidebar-icon svg {
  width: 22px; height: 22px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
}
.sidebar-backdrop { display: none; }

/* Colapsar a solo iconos (2026-07-28, petición del usuario) -- botón dentro
   del propio sidebar, estado recordado en localStorage (app.js). El texto no
   se quita del DOM (solo se oculta) para que el `title` de cada `.sidebar-item`
   siga sirviendo de tooltip con el nombre completo al pasar el ratón. */
.sidebar-collapse-btn {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  flex-shrink: 0; width: 100%; height: 32px; margin-top: 8px;
  border: none; border-radius: var(--radius); background: none;
  color: var(--dim); font-size: var(--fs-sm); font-weight: 600;
}
.sidebar-collapse-btn:hover { background: var(--bg3); color: var(--tx); }
.sidebar:is(.collapsed, .rail-auto) { width: var(--side-w-collapsed); }

/* ── EL RAIL SE ENCIENDE SOLO EN TABLET (2026-08-27) ────────────────────────
   Entre 761 y 1180 la aplicacion era escritorio con el menu fijo de 240px, asi
   que un iPad 10 en vertical (820) dejaba 532px para unas tablas pensadas para
   1400 -- MENOS que un iPad mini (744), que al caer por debajo de 760 recibia la
   maquetacion movil y dejaba 716. La tablet mas grande ensenaba menos que la
   pequena.

   La clase la pone `app.js` con un `matchMedia`, y es una clase DISTINTA de
   `collapsed` a proposito: `collapsed` es la preferencia del usuario y vive en
   localStorage, asi que escribirla al cruzar un ancho le cambiaria su ajuste de
   escritorio por haber girado la tablet. Aqui el ancho lo decide el viewport y la
   preferencia sigue siendo suya, intacta.

   Las ~26 reglas del rail no se duplican aqui: sus selectores aceptan las DOS
   clases (`.sidebar:is(.collapsed, .rail-auto)`). Duplicarlas seria garantizar
   que las dos copias divergen. */
@media (min-width: 761px) and (max-width: 1180px) {
  /* Colapsar a mano no significa nada cuando el ancho manda: el boton se va, y
     con el la posibilidad de dejar el menu en un estado que el viewport va a
     deshacer en el siguiente repintado. */
  .sidebar-collapse-btn { display: none; }
}

/* ── LO QUE SE TOCA CON UN DEDO MIDE 44px (2026-08-27) ──────────────────────
   El bloque movil cambiaba el espaciado de tarjetas y pantallas y NO agrandaba
   un solo elemento interactivo: un pulgar recibia exactamente los mismos tamanos
   que un raton. El ⋮ de una fila median 26x26, el boton pequeno 27 de alto y los
   iconos de la barra 32.

   Las referencias: Apple recomienda 44x44, Material 48x48, y WCAG 2.2 fija el
   minimo AA en 24x24 con reglas de separacion. O sea que tecnicamente pasaban la
   norma y fallaban la usabilidad real -- un ⋮ de 26px es donde se falla el toque
   y se acaba pulsando la FILA, que navega a otro sitio.

   Se aplica por ancho Y por tipo de puntero: una tablet en horizontal son 1180
   pero un portatil con pantalla tactil tambien se toca con el dedo, y ahi lo que
   manda no es el ancho. `pointer: coarse` no es un punto de corte, asi que la
   guarda de anchos no lo cuenta.

   DOS TECNICAS, y la eleccion importa:
     · Crecer DE VERDAD lo que va suelto (botones, campos, pestanas): son
       elementos con su propio sitio y engordarlos no descuadra nada.
     · Crecer SOLO EL AREA en lo que vive dentro de una fila densa (el ⋮, el
       chevron de una tarjeta): subirlos a 44 visuales estiraria el alto de cada
       fila de la tabla. El area invisible se toca igual y no se ve. */
@media (max-width: 1180px), (pointer: coarse) {
  /* Los que crecen de verdad */
  .btn { height: 44px; }
  .btn-sm { height: 38px; padding: 0 14px; }
  .field input, .field select, .field textarea { height: 44px; }
  .filter-toggle { height: 44px; }
  .pager-size { height: 38px; }
  .subtab { height: 44px; }
  .bell-btn, .user-menu-btn, .menu-toggle { width: 44px; height: 44px; }
  /* La barra superior tiene 48px de alto: cuatro botones de 44 caben, pero sin
     separacion se tocan entre si. WCAG pide 8px entre objetivos contiguos. */
  .topbar { gap: 4px; }

  /* Los que crecen SOLO en area. El pseudo-elemento se centra sobre el boton y
     se lleva el toque; `pointer-events` no hace falta porque un ::after hereda
     el del padre, pero el z-index si: sin el, la celda de al lado puede quedar
     por encima y robar el toque en el borde. */
  .kebab-btn, .card-more-btn { position: relative; }
  .kebab-btn::after, .card-more-btn::after {
    content: ''; position: absolute; z-index: 1;
    top: 50%; left: 50%; transform: translate(-50%, -50%);
    width: 44px; height: 44px;
  }
  /* Y separacion, que sin ella un objetivo de 44 pegado a otro de 44 sigue
     siendo un sitio donde se falla: el dedo cae entre los dos. */
  .kebab-btn { margin-left: 4px; }
}
.sidebar:is(.collapsed, .rail-auto) .sidebar-item { justify-content: center; padding: 11px 0; gap: 0; }
.sidebar:is(.collapsed, .rail-auto) .sidebar-label { display: none; }
/* Con el menú compacto el icono es lo ÚNICO que se ve: se agranda para que siga
   siendo identificable de un vistazo sin el texto al lado. */
.sidebar:is(.collapsed, .rail-auto) .sidebar-icon svg { width: 24px; height: 24px; }
/* Compacto: al pulsar un grupo se despliegan sus hijos AQUÍ MISMO, como iconos
   más pequeños (2026-08-08, petición del usuario con las capturas de Azure
   DevOps). Antes solo salían en el flyout al pasar el ratón, así que en una
   pantalla táctil -- donde no hay hover -- el menú compacto no llevaba a
   ninguna pantalla. El flyout se queda: es lo que da los NOMBRES, que un icono
   suelto no dice.
   La banda gris es lo que hace de agrupador sin necesitar sangría: sin texto no
   hay dónde sangrar, y una tira de iconos sueltos no se leería como "estos
   cuelgan de aquel". El caret sobra (no hay texto que acompañar). */
.sidebar:is(.collapsed, .rail-auto) .nav-caret { display: none; }
.sidebar:is(.collapsed, .rail-auto) .nav-children {
  margin: 2px 0; padding: 3px 0;
  border-left: none;                       /* la guía vertical no cabe sin sangría */
  background: var(--bg3); border-radius: var(--radius);
}
.sidebar:is(.collapsed, .rail-auto) .nav-child { justify-content: center; padding: 8px 0; gap: 0; }
/* Más pequeños que el icono del grupo (24px), que es lo que dice de un vistazo
   cuál es el nivel 1 y cuál el 2 cuando no hay texto que lo diga. */
.sidebar:is(.collapsed, .rail-auto) .nav-child .sidebar-icon svg { width: 18px; height: 18px; }
/* El grupo que contiene la pantalla actual se rellena: con los hijos plegados, la
   barra sola es una señal demasiado débil para decir dónde estás. */
.sidebar:is(.collapsed, .rail-auto) .nav-parent.in-path { background: var(--a-soft); }
.sidebar:is(.collapsed, .rail-auto) .nav-parent.in-path::before { left: 2px; }
/* La pantalla abierta, dentro de la banda: relleno propio y barra de acento
   pegada al borde, como en la captura. Es la única marca que queda cuando no hay
   texto, así que tiene que ganarle al gris de la banda. */
.sidebar:is(.collapsed, .rail-auto) .nav-child.active { position: relative; background: var(--a-soft); }
.sidebar:is(.collapsed, .rail-auto) .nav-child.active::before {
  content: ''; position: absolute; left: 2px; top: 5px; bottom: 5px;
  width: 3px; border-radius: 2px; background: var(--a);
}

/* Flyout del menú colapsado (2026-08-04, petición del usuario: al pasar por
   encima de un icono debe salir su nombre y poderse pulsar, como en Overa HR).
   Va en position:fixed y colgado del <body> a propósito: el sidebar tiene
   overflow-x:hidden para el modo compacto, así que un desplegable absoluto
   dentro de él quedaría recortado. La posición vertical la calcula app.js a
   partir del icono apuntado.
   Desde 2026-08-08 no es una etiqueta con un nombre sino el PANEL del grupo con
   sus hijos, como el menú de Boards de Azure DevOps: con el menú compacto los
   hijos están ocultos, así que este panel es el único camino hasta ellos. */
.sidebar-flyout {
  position: fixed; z-index: 160; display: none;
  min-width: 190px; padding: 6px; border-radius: var(--radius);
  background: var(--bg2); border: 1px solid var(--bd);
  box-shadow: 0 4px 14px rgba(30,37,54,.12);
  font-size: var(--fs-md); color: var(--tx); white-space: nowrap;
}
.sidebar-flyout.open { display: block; }
.flyout-title {
  padding: 6px 10px 8px; color: var(--dim); font-weight: 700;
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em;
  border-bottom: 1px solid var(--bd); margin-bottom: 4px;
}
.flyout-item {
  display: flex; align-items: center; gap: 9px;
  padding: 8px 10px; border-radius: var(--radius);
  cursor: pointer; color: var(--tx); font-size: var(--fs-sm);
}
/* Mismo tamaño de icono que el nivel 2 del menú: es el mismo nivel. */
.flyout-item .sidebar-icon svg { width: 18px; height: 18px; }
.flyout-item:hover { background: var(--bg3); }
.flyout-item.active { background: var(--a-soft); color: var(--a2); font-weight: 600; }

/* ── Miga de pan (2026-08-08) ────────────────────────────────────────────────
   Va fuera de las pantallas (ver index.html), en la franja de arriba del área de
   contenido. Dice el camino completo: ÁREA › pantalla › detalle. El área es
   pulsable solo si tiene pantalla propia (hoy solo Sistema); si no, es texto:
   pintarla como enlace prometería algo que no existe. La pantalla sí lo es cuando
   estás en un detalle, que es el gesto de
   "volver al listado". El último tramo va en color de texto normal y con peso:
   es dónde estás, no un sitio al que ir. */
/* EL ALTO NO DEPENDE DE LO QUE LLEVE DENTRO (2026-08-27). Con solo `padding`, la
   barra medía 36 px en casi todas las pantallas y 45 en la consola de TradingView
   --que mete botones de 27 px--, así que al navegar el cuerpo daba un salto. El
   botón de volver lo habría empeorado: aparece y desaparece según haya escalón
   arriba, o sea que el salto sería en casi cada navegación. El `min-height` lo deja
   quieto, y de paso endereza lo que ya estaba torcido. */
.breadcrumb {
  flex-shrink: 0; display: flex; align-items: center; flex-wrap: wrap; gap: 7px;
  padding: 6px 24px; min-height: 40px; border-bottom: 1px solid var(--bd);
  background: var(--bg2); font-size: var(--fs-sm); color: var(--dim);
}
.breadcrumb[hidden] { display: none; }
.bc-group { display: inline-flex; align-items: center; gap: 6px; color: var(--dim); }
.bc-icon { display: inline-flex; }
/* El icono del área, aún más pequeño que en el menú: aquí acompaña al texto, no
   tiene que identificarse solo. */
.bc-icon svg {
  width: 15px; height: 15px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.7;
  stroke-linecap: round; stroke-linejoin: round;
}
.bc-sep { color: var(--bd2); }
.bc-current { color: var(--tx); font-weight: 600; }
.bc-link {
  border: none; background: none; padding: 0; cursor: pointer;
  color: var(--a2); font-size: inherit; font-family: inherit;
}
.bc-link:hover { text-decoration: underline; }
/* ACCIONES EN LA PROPIA BARRA DE LA MIGA (2026-08-27). Para lo que gobierna el
   PROGRAMA de una máquina --arrancar, reiniciar, la ventana-- que no es contenido
   de la pantalla y ocupando una fila del cuerpo empuja abajo lo que sí se lee.
   A la derecha y con su propio scroll: en móvil son ocho botones y la alternativa
   sería que la miga creciera a tres líneas. */
.bc-fill { flex: 1; min-width: 8px; }
.bc-actions {
  display: flex; align-items: center; gap: 5px; flex-wrap: nowrap;
  overflow-x: auto; max-width: 100%; min-width: 0; padding-bottom: 1px;
  scrollbar-width: thin;
}
.bc-actions .tvc-btn { flex: 0 0 auto; }
/* ── «VOLVER» ES UN ICONO DE ESTA BARRA (2026-08-27, petición del usuario) ──
   Antes cada pantalla de detalle pintaba su propio «← Cuentas» / «← Infraestructura»
   en la cabecera del cuerpo: seis botones en cinco ficheros, cada uno con su texto
   y su sitio, y en Arquitectura tres formas de irse a la vez. Ahora hay uno, en la
   barra que ya dice el camino y por tanto ya sabe cuál es el escalón de arriba.
   A la derecha de TODO --después incluso de las acciones de la pantalla-- para que
   no cambie de sitio según cuántos botones traiga cada una. */
.bc-back {
  flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center;
  width: 27px; height: 27px; margin-left: 6px; padding: 0;
  border: 1px solid var(--bd2); border-radius: var(--radius);
  background: var(--bg3); color: var(--tx); cursor: pointer;
}
.bc-back:hover { background: var(--bd2); }
.bc-back:focus-visible { outline: 2px solid var(--a2); outline-offset: 1px; }
.bc-back-icon svg {
  width: 17px; height: 17px; display: block;
  fill: none; stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}
/* En la miga el texto sobra: son los mismos botones de siempre y aquí compiten
   con la ruta. El `<span>` SIGUE en el DOM --es lo que lee un lector-- y su
   `title` dice lo mismo al pasar por encima. */
@media (max-width: 1100px) {
  .bc-actions .tvc-btn span { display: none; }
  .bc-actions .tvc-btn { padding: 0 8px; }
}

.main-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; min-width: 0; }
.screen { display: none; flex: 1; overflow-y: auto; padding: 24px; flex-direction: column; gap: 16px; }
.screen.active { display: flex; }
/* ── El contenido no se estira sin fin (2026-08-27) ─────────────────────────
   Se acota a los HIJOS y no a `.screen`: ella es el contenedor con el scroll, y
   limitarla ahi dejaria la barra de desplazamiento flotando en mitad de la
   pantalla en vez de pegada al borde.

   Las tablas anchas no pierden nada: siguen desbordando DENTRO de su tarjeta,
   que es lo que hace `.table-scroll`, y ahora ademas con la primera columna
   fija. Una pantalla que de verdad necesite todo el ancho --un mapa, un
   diagrama-- se sale con `.full-bleed`. */
.screen.active > * { width: 100%; max-width: var(--content-max); margin-inline: auto; }
.screen.active > .full-bleed { max-width: none; }

.page-header { display: flex; align-items: center; gap: 12px; }
.page-title { font-size: var(--fs-lg); font-weight: 700; flex: 1; }

/* Sub-pestañas dentro de una pantalla (estilo panel Overa: subrayado en el
   activo, sin fondo). Las usa la pantalla unificada de Importar. */
.subtabs { display: flex; gap: 4px; border-bottom: 1px solid var(--bd); }
.subtab {
  height: 34px; padding: 0 14px; border: none; background: none; color: var(--dim);
  font-size: var(--fs-md); font-weight: 500; border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.subtab:hover { color: var(--tx); }
.subtab.active { color: var(--a2); font-weight: 600; border-bottom-color: var(--a); }
/* Los paneles de sub-pestaña replican el espaciado que .screen da a sus hijos
   (las cards vienen sueltas del innerHTML de cada módulo). */
.subtab-pane { display: flex; flex-direction: column; gap: 16px; }
.subtab-pane.hidden { display: none !important; }

/* ── Filtros ocultables (estilo panel Overa) ───────────────────────────────
   Los selects de filtro ocupaban sitio permanente aunque casi nunca se
   cambien. Ahora se colapsan detrás del botón de embudo: la barra deja a la
   vista solo un resumen de qué hay aplicado ("Sin filtros" / "Estado: Activa
   · Tipo: …") y el panel se despliega al pulsar. El embudo se pinta relleno
   cuando hay algún filtro activo, para que no se pueda "perder" un filtro
   puesto con el panel cerrado. */
.filter-bar { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.filter-summary { font-size: var(--fs-nm); color: var(--dim); flex: 1; min-width: 0; }
.filter-summary strong { color: var(--tx); font-weight: 600; }
.filter-toggle {
  display: inline-flex; align-items: center; gap: 6px; height: 30px; padding: 0 11px;
  border: 1px solid var(--bd2); border-radius: var(--radius); background: var(--bg2);
  color: var(--dim); font-size: var(--fs-sm); font-weight: 600;
}
.filter-toggle:hover { background: var(--bg3); color: var(--tx); }
/* El COLOR indica "hay filtros aplicados" (no se puede perder un filtro puesto
   con el panel cerrado); la ROTACIÓN del caret indica "panel desplegado". Son
   dos señales independientes, así que no se pisan entre ellas. */
.filter-toggle.has-filters { border-color: var(--a); background: var(--a-soft); color: var(--a2); }
.filter-toggle.has-filters:hover { background: var(--bg3); }
.filter-toggle .filter-caret { display: inline-block; transition: transform .15s ease; font-size: 9px; }
.filter-toggle.open .filter-caret { transform: rotate(180deg); }
.filter-toggle .filter-count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px;
  padding: 0 4px; border-radius: 8px; background: var(--a); color: #fff; font-size: var(--fs-xs); font-weight: 700;
}
/* ── El panel de filtros es un CAJÓN LATERAL, en móvil y en escritorio ──────
   (2026-08-13, petición del usuario) El menú ☰ entra por la izquierda; los
   filtros entran por la DERECHA, con el mismo fondo oscurecido, la misma altura
   (bajo la barra superior) y la misma regla: se cierra al terminar, igual que
   elegir una entrada del menú lo cierra.
   Esto vivía SOLO en la media query de móvil y en escritorio el panel se
   desplegaba en línea bajo la barra. Eran dos disposiciones distintas de la
   misma pantalla -- una fila de campos que se parte sola y una columna -- con
   sus dos botones de "Restablecer", y había que acordarse de las dos al añadir
   un tipo de campo. Se queda la que ya estaba probada en el móvil. */
.filter-panel:not([hidden]) {
  position: fixed; top: var(--nav-h); right: 0; bottom: 0; left: auto;
  width: min(88vw, 360px); z-index: 190; margin: 0;
  display: flex; flex-direction: column;
  background: var(--bg); border: none; border-left: 1px solid var(--bd); border-radius: 0;
  box-shadow: -2px 0 24px rgba(0,0,0,.2);
  padding: 0;
}
/* Los campos, en una columna y a todo el ancho del cajón: `.fields-row` es una
   FILA (pensada para un formulario ancho) y aquí no hay ancho que repartir. */
.filter-panel .fields-row { flex-direction: column; align-items: stretch; gap: 12px; }
.filter-panel .fields-row .field { flex: none; min-width: 0; }
.filter-panel .ms { min-width: 0; }
.filter-panel[hidden] { display: none; }
/* El cuerpo SÍ es una caja: es la que hace scroll entre la cabecera y el pie. */
.filter-panel-body { display: block; padding: 14px; overflow-y: auto; flex: 1; }
.filter-panel-head {
  display: flex;
  align-items: center; justify-content: space-between;
  padding: 12px 14px; border-bottom: 1px solid var(--bd);
  font-weight: 600; font-size: var(--fs-md);
}
.filter-close {
  width: 30px; height: 30px; border: 1px solid var(--bd2); border-radius: var(--radius);
  background: var(--bg2); color: var(--dim); font-size: var(--fs-md); line-height: 1;
}
.filter-close:hover { background: var(--bg3); color: var(--tx); }
.filter-panel-foot {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px;
  padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  border-top: 1px solid var(--bd); background: var(--bg2);
}
.filter-panel-foot .btn { height: 40px; }   /* objetivo táctil cómodo */
/* Fondo oscurecido, igual que el del menú: el cajón es modal (se cierra al
   pulsar fuera) y con el fondo a la vista no se sabría que hay que cerrarlo. */
.filter-backdrop:not([hidden]) {
  display: block; position: fixed; top: var(--nav-h); left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,.4); z-index: 180;
}
.filter-backdrop[hidden] { display: none; }
/* Con el cajón abierto, el fondo no debe poder desplazarse por debajo. */
body.filters-open { overflow: hidden; }
/* Celdas de tarjeta móvil: en escritorio no existen (ver la media query). El
   valor agrupado se COPIA aquí y la celda original sigue en su columna, así que
   la tabla de escritorio no cambia. */
table.data-table td .cell-extra { display: none; }
/* El chevrón de desplegar la tarjeta no existe en escritorio: ahí están todas
   las columnas. Con display:none sale además del recorrido del tabulador, que si
   no añadiría una parada por fila donde antes no había ninguna. */
.card-more-btn { display: none; }
/* Lectura numérica opcional de la barra (nº de operaciones, neto del rango…):
   contexto del filtro, no un filtro. */
.filter-readout { font-size: var(--fs-sm); color: var(--dim); white-space: nowrap; }
.filter-readout strong { font-weight: 700; }
/* El rango de fechas por defecto se muestra atenuado: es contexto ("de qué
   periodo son estas cifras"), no un filtro que el usuario haya puesto. */
.filter-ctx { color: var(--dim2); }

/* Rango de fechas con atajos (tipo 'daterange' de filters.js) */
.field-daterange .dr-inputs { display: flex; align-items: center; gap: 6px; }
.field-daterange .dr-inputs input { min-width: 0; }
.field-daterange .dr-sep { color: var(--dim2); font-size: var(--fs-sm); }
.dr-presets { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.dr-preset {
  height: 26px; padding: 0 10px; border: 1px solid var(--bd2); border-radius: var(--radius);
  background: var(--bg2); color: var(--dim); font-size: var(--fs-xs); font-weight: 600;
}
.dr-preset:hover { background: var(--a-soft); border-color: var(--a); color: var(--a2); }

/* ── Desplegable de selección múltiple (2026-08-05) ────────────────────────
   No se usa `<select multiple>` nativo: obliga a Ctrl+clic para elegir varias
   (nadie lo descubre) y no deja ver lo seleccionado si no cabe. Aquí el botón
   resume la selección y la lista de casillas se despliega debajo. */
.ms { position: relative; min-width: 168px; }
.ms-btn {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  /* 34px = la misma altura que `.field input/select/textarea`: en Notificaciones
     conviven en una fila con un <select> nativo y 36px los desalineaba. */
  height: 34px; padding: 0 10px; width: 100%;
  background: var(--input-bg); border: 1px solid var(--bd2); border-radius: var(--radius);
  color: var(--tx); font-size: var(--fs-base); text-align: left;
}
.ms-btn:hover { border-color: var(--dim2); }
/* Con algo seleccionado el control se tiñe de acento: hay que poder ver de un
   vistazo CUÁL de los cuatro filtros está actuando. */
.ms-btn.has-sel { border-color: var(--a); background: var(--a-soft); color: var(--a2); font-weight: 600; }
.ms-btn-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ms-caret { font-size: 9px; color: var(--dim); flex: none; }
.ms-btn[aria-expanded="true"] .ms-caret { transform: rotate(180deg); }
/* El panel NO scrollea: lo hace la lista de opciones (.ms-opts). Así el buscador
   y los atajos Todas/Ninguna siguen a la vista mientras se recorre la lista. */
.ms-panel {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 60;
  min-width: 100%; max-width: 320px; max-height: 300px;
  display: flex; flex-direction: column; overflow: hidden;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius);
  box-shadow: var(--sh-2); padding: 5px;
}
.ms-panel[hidden] { display: none; }
.ms-opts { overflow-y: auto; min-height: 0; }
.ms-search {
  flex: none; width: 100%; margin-bottom: 4px; padding: 6px 8px;
  border: 1px solid var(--bd); border-radius: 5px; background: var(--bg);
  font-size: var(--fs-base); color: var(--fg); font-family: inherit;
}
.ms-search:focus { outline: none; border-color: var(--a); box-shadow: 0 0 0 2px var(--a-soft); }
.ms-actions { display: flex; gap: 4px; align-items: center; padding: 2px 4px 6px; border-bottom: 1px solid var(--bd); margin-bottom: 4px; }
.ms-link { border: none; background: none; padding: 2px 6px; border-radius: 5px; color: var(--a); font-size: var(--fs-xs); font-weight: 600; }
.ms-link:hover:not(:disabled) { background: var(--a-soft); }
.ms-link:disabled { color: var(--dim2); cursor: default; }
.ms-count { margin-left: auto; padding-right: 4px; font-size: var(--fs-xs); color: var(--dim); }
.ms-opt { display: flex; align-items: center; gap: 8px; padding: 6px 8px; border-radius: 5px; cursor: pointer; font-size: var(--fs-base); white-space: nowrap; }
/* `display:flex` de arriba gana al `display:none` que el navegador aplica por el
   atributo `hidden`, así que sin esto el buscador no ocultaría nada. */
.ms-opt[hidden] { display: none; }
.ms-opt:hover { background: var(--bg3); }
.ms-opt input { flex: none; cursor: pointer; }

.card {
  background: var(--bg2);
  border: 1px solid var(--bd); border-radius: var(--radius-lg); padding: 20px;
  box-shadow: var(--sh-1);
}
.card-title { font-size: var(--fs-md); font-weight: 600; margin-bottom: 12px; }
/* Tarjetas que llevan a algún sitio: se elevan al pasar el ratón, para que se
   note que son pulsables sin tener que añadirles un botón. */
.card-link { cursor: pointer; transition: box-shadow .15s ease, border-color .15s ease; }
.card-link:hover { box-shadow: var(--sh-2); border-color: var(--bd2); }

/* ── Utilidades de texto y dato ────────────────────────────────────────────
   Estos tres patrones estaban repetidos a mano por todo el JS en estilos
   inline (`.hint` ~48 veces, `.muted` ~25, y el color del PnL ~150). Tenerlos
   como clase evita que cada pantalla invente su propia variante. */
.hint { font-size: var(--fs-xs); color: var(--dim); font-weight: 400; }

/* Avisos en bloque dentro de una tarjeta o modal. Barra lateral de color en vez
   de fondo saturado, para que el texto conserve su contraste (los tonos *-soft
   están calculados para llevar texto encima). `--info` es morado a propósito:
   el azul es el acento de acción y confundía "esto es un aviso" con "pulsa aquí". */
.notice {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 12px; margin: 10px 0; border-radius: var(--radius);
  border: 1px solid var(--bd); border-left: 3px solid var(--dim2);
  background: var(--bg); font-size: var(--fs-nm); line-height: 1.5;
}
.notice-icon { flex: none; line-height: 1.4; }
.notice-warn   { border-left-color: var(--warn);   background: var(--warn-soft); }
.notice-danger { border-left-color: var(--danger); background: var(--neg-soft); }
.notice-info   { border-left-color: var(--info);   background: var(--info-soft); }
.notice-ok     { border-left-color: var(--ok);     background: var(--pos-soft); }
.notice strong { font-weight: 600; }
.muted { color: var(--dim); }
/* Celdas que agrupan dos datos ("194.615 (41,3)", "2650.30 / 2655.80"): sin esto
   el navegador parte por el espacio y el par se lee como dos filas. */
.nowrap { white-space: nowrap; }

/* ── Gráfico de un trade: cabecera de resumen y selector de temporalidad ──────
   Sigue la forma de la cabecera de un terminal (cifra grande a la izquierda,
   datos clave a la derecha, temporalidades en una fila bajo ellos), pero lo que
   resume es la OPERACIÓN, no el instrumento. */
.tc-head {
  display: flex; flex-wrap: wrap; gap: 16px; justify-content: space-between;
  align-items: flex-start; padding: 10px 0 12px; border-bottom: 1px solid var(--bd);
}
.tc-net { font-size: 26px; font-weight: 700; line-height: 1.1; font-variant-numeric: tabular-nums; }
/* auto-fit y no un nº fijo de columnas: en el modal estrecho del móvil los cuatro
   pares caben en dos filas sin sacar scroll horizontal. */
.tc-kvs { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 2px 18px; }
.tc-kv { display: flex; gap: 10px; justify-content: space-between; font-size: var(--fs-sm); }
.tc-kv span:last-child { font-variant-numeric: tabular-nums; }
.tc-legs { font-size: var(--fs-sm); margin-bottom: 4px; }
.tc-tfs { display: flex; flex-wrap: wrap; gap: 2px; margin: 10px 0 6px; }
/* Botón plano, no un .btn: son 5 o 6 seguidos y con borde cada uno la fila pesa
   más que el gráfico que gobierna. El activo se marca con color y subrayado. */
.tf-btn {
  background: none; border: none; cursor: pointer; padding: 5px 10px;
  font-size: var(--fs-sm); font-weight: 500; color: var(--dim);
  border-bottom: 2px solid transparent; border-radius: 0;
}
.tf-btn:hover { color: var(--tx); background: var(--bg3); }
.tf-btn.active { color: var(--a); font-weight: 700; border-bottom-color: var(--a); }
/* Temporalidad que NO esta guardada: se puede PEDIR al terminal, no dibujar. Se
   ve atenuada y con su flecha para que se distinga de un vistazo de las que si
   hay -- si se pintara igual, pulsarla parecería un fallo en vez de un encargo.
   No se oculta: sin ella el usuario no sabe que esa temporalidad existe. */
.tf-btn-pedible { color: var(--dim2); font-style: italic; }
.tf-btn-pedible:hover { color: var(--a); background: var(--a-soft); }
.pos { color: var(--pos); }
.neg { color: var(--neg); }

/* QUE CAMBIO ENTRE DOS VERSIONES de una estrategia (2026-08-29). Monoespaciada y
   con el fondo por linea, no solo el texto en color: en un diff, lo que se busca
   primero es DONDE estan los cambios, y un color de letra sobre el mismo fondo no
   se ve de un vistazo. Los tonos salen de --pos/--neg con transparencia, asi que
   valen en los dos temas sin declararlos dos veces. */
.sv-diff {
  max-height: 320px; overflow: auto; border: 1px solid var(--bd);
  border-radius: var(--radius); background: var(--bg); padding: 6px 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-xs); line-height: 1.45;
}
.sv-diff-linea { padding: 0 8px; white-space: pre-wrap; word-break: break-word; }
.sv-diff-mas { background: color-mix(in srgb, var(--pos) 14%, transparent); color: var(--tx); }
.sv-diff-menos { background: color-mix(in srgb, var(--neg) 14%, transparent); color: var(--tx); }
.sv-diff-igual { color: var(--dim2); }
/* El salto entre dos trozos con cambios. Sin el, dos zonas lejanas del fichero
   parecerian lineas seguidas. */
.sv-diff-hueco { padding: 2px 8px; color: var(--dim); background: var(--bg2); }
/* Completa el trío: --warn ya está en la paleta como color de TEXTO (con su
   ratio verificado), pero no tenía clase y se escribía a mano en cada sitio. */
.warn { color: var(--warn); }
/* Una línea del histórico de una tarea: qué pasó, cuándo y quién, con la nota
   debajo ocupando todo el ancho (una nota puede ser larga y no debe empujar a
   la fecha fuera de la vista). */
.notif-event {
  display: grid; grid-template-columns: auto auto 1fr; gap: 4px 8px; align-items: center;
  padding: 8px 0; border-bottom: 1px solid var(--bd); font-size: var(--fs-xs);
}
.notif-event:last-child { border-bottom: none; }
/* Chip de variación (+3,2% / −1,8%) para acompañar a un KPI: el fondo pastel lo
   hace legible de un vistazo sin robar protagonismo a la cifra principal. */
.chip { display: inline-flex; align-items: center; gap: 3px; padding: 1px 7px; border-radius: 20px; font-size: var(--fs-xs); font-weight: 600; }
.chip-pos { background: var(--pos-soft); color: var(--pos); }
.chip-neg { background: var(--neg-soft); color: var(--neg); }
.chip-neutral { background: var(--bg3); color: var(--dim); }
/* Contenedor de tabla ancha: el scroll horizontal se queda DENTRO de la tarjeta
   en vez de desbordar la pantalla (13 sitios lo hacían inline). */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }

/* ── ESQUELETOS DE CARGA (2026-08-27) ───────────────────────────────────────
   El estado de carga era un spinner, y hay un dato medido que lo condena:
   `/api/wealth` tarda ~4,3 s y `/api/dashboard` ~3,9 s (REVISION-SAAS.md §D).
   Cuatro segundos de spinner se leen como "esto se ha roto"; cuatro segundos de
   esqueleto se leen como "esta llegando", y ademas ensenan la FORMA de lo que
   viene, asi que el ojo ya sabe donde va a estar cada cosa y no tiene que
   recolocarse cuando llega.

   El brillo va de izquierda a derecha con un degradado que se desplaza. Bajo
   `prefers-reduced-motion` se queda quieto: sigue diciendo "aqui viene algo" sin
   moverse, que es lo que se pide. Nunca se anima la opacidad de toda la pagina. */
.sk { border-radius: var(--radius); background: var(--bg3); position: relative; overflow: hidden; }
.sk::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, var(--bg2), transparent);
  opacity: .55; transform: translateX(-100%);
  animation: sk-brillo 1.4s ease-in-out infinite;
}
@keyframes sk-brillo { to { transform: translateX(100%); } }
@media (prefers-reduced-motion: reduce) {
  .sk::after { animation: none; transform: none; opacity: .28; }
}
.sk-wrap { display: flex; flex-direction: column; gap: 16px; }
/* Las tarjetas de cifra: la misma rejilla que usan de verdad, para que al llegar
   los datos nada se mueva de sitio. */
.sk-kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); gap: 14px; }
.sk-kpi { height: 92px; }
.sk-card { height: 190px; }
.sk-chart { height: 260px; }
/* Una fila por linea de tabla, con la cabecera algo mas oscura: es lo que da la
   silueta de "aqui va una tabla" sin fingir columnas que igual no coinciden. */
.sk-table { display: flex; flex-direction: column; gap: 6px; }
.sk-row { height: 34px; }
.sk-row.sk-head { height: 38px; background: var(--a-soft); }
.sk-text { height: 13px; }
.sk-text.sk-short { width: 42%; }

/* ── PALETA DE COMANDOS (Ctrl/Cmd+K, 2026-08-27) ────────────────────────────
   Veinte pantallas, N cuentas y miles de operaciones, y la unica forma de llegar
   a algo era navegar. Todo va con tokens, asi que sale bien en los dos temas sin
   una sola regla aparte. */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 260;   /* sobre los modales (200), bajo los avisos (300) */
  background: rgba(0,0,0,.32);
  display: flex; align-items: flex-start; justify-content: center;
  /* Arriba y no centrada: al escribir, la lista CRECE hacia abajo. Centrada, la
     caja se movería con cada tecla y el ojo perseguiria el cuadro de texto. */
  padding: 12vh 16px 16px;
}
.cmdk-overlay[hidden] { display: none; }
.cmdk-box {
  width: 100%; max-width: 560px; background: var(--modal-bg);
  border: 1px solid var(--bd); border-radius: var(--radius-lg);
  box-shadow: var(--sh-2); overflow: hidden;
  display: flex; flex-direction: column; max-height: 70vh;
}
.cmdk-input {
  width: 100%; height: 52px; padding: 0 16px; border: none; border-bottom: 1px solid var(--bd);
  background: var(--modal-bg); color: var(--tx); font-size: var(--fs-lg); font-family: inherit;
}
.cmdk-input:focus { outline: none; background: var(--input-bg-focus); }
.cmdk-list { overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.cmdk-item {
  display: flex; align-items: baseline; gap: 10px; width: 100%; text-align: left;
  padding: 9px 10px; border: none; border-radius: var(--radius); background: none;
  color: var(--tx); font-size: var(--fs-base); font-family: inherit; cursor: pointer;
}
.cmdk-item:hover, .cmdk-item.sel { background: var(--a-soft); color: var(--a2); }
/* La etiqueta de tipo va PRIMERO y con ancho fijo: asi las dos clases de
   resultado (pantalla y cuenta) quedan alineadas y se distinguen sin leerlas. */
.cmdk-kind {
  flex: none; width: 62px; font-size: var(--fs-xs); color: var(--dim2);
  text-transform: uppercase; letter-spacing: .04em;
}
.cmdk-item.sel .cmdk-kind { color: var(--a2); }
.cmdk-label { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmdk-path { flex: none; font-size: var(--fs-xs); color: var(--dim); }
.cmdk-empty { padding: 18px 12px; text-align: center; color: var(--dim); font-size: var(--fs-sm); }
.cmdk-foot {
  display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--bd);
  background: var(--bg3); font-size: var(--fs-xs); color: var(--dim);
}
.cmdk-foot kbd {
  display: inline-block; min-width: 18px; padding: 1px 5px; margin-right: 3px;
  border: 1px solid var(--bd2); border-bottom-width: 2px; border-radius: 4px;
  background: var(--bg2); font-family: inherit; font-size: 10px; color: var(--tx);
}
/* El fondo no se desplaza por debajo, igual que con el cajon de filtros. */
body.cmdk-open { overflow: hidden; }
/* En movil la caja ocupa mas y sube: el teclado virtual se come media pantalla. */
@media (max-width: 760px) {
  .cmdk-overlay { padding: 6vh 10px 10px; }
  .cmdk-foot { display: none; }   /* no hay teclas que enseñar sin teclado */
}

/* ── LA PRIMERA COLUMNA SE QUEDA AL DESPLAZAR (2026-08-27) ──────────────────
   El scroll lateral resolvia que la tabla cupiera, y creaba otro problema: al
   desplazarse se pierde de vista la primera columna, que es JUSTO la que dice de
   que fila estas leyendo. Se acaba mirando una cifra sin saber de que cuenta es.

   Se aplica en todo lo que NO sea movil, y no solo en tablet. En movil las
   tablas son tarjetas --sus `td` pasan a `display: flex`, asi que fijarlos ahi
   no significaria nada-- pero en escritorio una tabla de veinte columnas como la
   de Cuentas sigue desbordando dentro de su tarjeta, y ahi el problema es el
   mismo. Solo dentro de `.table-scroll`: sin scroll no hay nada que fijar.

   ⚠️ HACE FALTA FONDO OPACO, y no vale uno solo. Una celda transparente deja ver
   por debajo lo que pasa desplazandose, asi que la columna fija se convierte en
   un borron. Y el fondo cambia con el estado de la fila (cebra, hover, activa),
   asi que hay que repetir los cuatro: con uno fijo, la fila bajo el raton se
   veria del color del hover salvo su primera celda.

   Las capas, de abajo arriba: celda normal (auto) < primera columna (2) <
   cabecera (1, la que ya estaba) ... y la ESQUINA necesita ganar a las dos, de
   ahi el 3. El menu de fila flota en 210, asi que sigue por encima de todo. */
@media (min-width: 761px) {
  .table-scroll table.data-table th:first-child,
  .table-scroll table.data-table td:first-child {
    position: sticky; left: 0;
  }
  .table-scroll table.data-table td:first-child {
    z-index: 2; background: var(--bg2);
  }
  /* La esquina: fija por arriba (lo era ya) Y por la izquierda. */
  .table-scroll table.data-table th:first-child { z-index: 3; }

  /* Los cuatro fondos de fila, repetidos en la celda fija para que no se note
     que esta despegada del resto. El orden importa: son la misma cascada que
     abajo, asi que activa gana a hover y hover gana a cebra. */
  .table-scroll table.data-table tbody tr:nth-child(even) td:first-child { background: var(--zebra); }
  .table-scroll table.data-table tr:hover td:first-child { background: var(--bg3); }
  .table-scroll table.data-table tr.clickable.active td:first-child { background: var(--a-soft); }
  .table-scroll table.data-table tr.row-total td:first-child { background: var(--bg3); }

  /* Una linea que separa lo fijo de lo que pasa por debajo. Sin ella, al
     desplazar el texto se mete "dentro" de la columna fija sin transicion. */
  .table-scroll table.data-table th:first-child,
  .table-scroll table.data-table td:first-child { border-right: 1px solid var(--bd); }
}

/* Acento lateral de estado en tarjetas de KPI (estilo panel Overa: barra de
   color a la izquierda que califica la lectura de un vistazo). Se aplica
   añadiendo .card-accent-{ok,warn,danger,info} junto a .card. */
.card-accent-ok, .card-accent-warn, .card-accent-danger, .card-accent-info { border-left-width: 3px; border-left-style: solid; }
.card-accent-ok { border-left-color: var(--ok); }
.card-accent-warn { border-left-color: var(--warn); }
.card-accent-danger { border-left-color: var(--danger); }
.card-accent-info { border-left-color: var(--info); }

.grid { display: grid; gap: 14px; }
/* ── LAS REJILLAS SE DECLARAN POR ANCHO MINIMO, NO POR NUMERO DE COLUMNAS ───
   (2026-08-27.) Eran columnas fijas que colapsaban de golpe a UNA a 760px, asi
   que una fila de cuatro indicadores iba 4 -> 4 -> 4 -> 1: a 820px eran cuatro
   tarjetas de ~130px con la cifra partida en dos lineas, y a 759 cuatro tarjetas
   apiladas a pantalla completa. Nunca dos y dos, que es lo que pide ese ancho.

   El patron bueno YA estaba en este mismo fichero -- `.webparts` y
   `.wealth-readings` usan auto-fit/minmax y se reacomodan solas sin una sola
   media query. Esto es aplicarlo a las cinco.

   EL NUMERO DE LA CLASE PASA A SER UN TECHO, no una obligacion: `.grid-4` son
   "hasta cuatro", y por debajo caen las que quepan. El minimo de cada una sale
   de lo que de verdad necesita su contenido, y por eso NO es el mismo para las
   cinco: una rejilla de 6 lleva cifras cortas y una de 2 lleva tarjetas con
   texto.

   auto-fit y no auto-fill: con menos elementos que columnas, `auto-fit` colapsa
   las pistas vacias y reparte el ancho entre los que hay. Con `auto-fill` una
   fila de dos tarjetas en una `.grid-4` dejaria dos huecos fantasma a la
   derecha, que se leen como "falta algo aqui".

   `min()` en el minimo para que en una pantalla mas estrecha que el propio
   minimo la celda no desborde: a 360px, `min(210px, 100%)` vale 100% y la
   tarjeta cabe en vez de sacar scroll horizontal. */
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(260px, 100%), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(210px, 100%), 1fr)); }
.grid-5 { grid-template-columns: repeat(auto-fit, minmax(min(175px, 100%), 1fr)); }
.grid-6 { grid-template-columns: repeat(auto-fit, minmax(min(150px, 100%), 1fr)); }

/* Dos paneles del cuadro de mando en paralelo (operaciones abiertas / cerradas
   hoy). Llevan tablas anchas dentro, así que se apilan en cuanto la pantalla no
   da para dos columnas cómodas. */
.dash-2col { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
@media (max-width: 1100px) { .dash-2col { grid-template-columns: 1fr; } }

/* Hueco de panel con carga propia: existe siempre en el DOM (es el ancla donde
   se inyecta la tarjeta cuando llegan sus datos), pero `display:contents` hace
   que NO sea él la celda del grid sino la tarjeta de dentro. Así un hueco vacío
   -- panel sin datos, que no pinta nada -- no deja una columna en blanco ni un
   gap suelto, que es lo que evitaba el antiguo `_twoColRow`. */
.dash-slot { display: contents; }
/* Si uno de los dos huecos de la pareja está vacío (ese panel no tiene datos),
   el que sí tiene ocupa el ancho completo en vez de dejar media fila en blanco
   -- comportamiento que antes daba _twoColRow en JS y que aquí se consigue sin
   tocar el marcado. `:empty` acierta porque el hueco se rellena con innerHTML. */
.dash-2col:has(> .dash-slot:empty) > .dash-slot > * { grid-column: 1 / -1; }

/* Punto de estado (proceso vivo / parado). El color lo pone quien lo usa. */
.status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex: none; }

/* ── Menú de acciones de fila (⋮) ─────────────────────────────────────────
   (2026-08-05.) Sustituye a los 3 iconos sueltos que llevaba cada fila. El menú
   se cuelga del <body> en position:fixed porque las tablas van dentro de un
   contenedor con overflow-x:auto que recortaría un desplegable absoluto. */
.kebab-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; height: 26px; border: 1px solid transparent; border-radius: 6px;
  background: none; color: var(--dim); font-size: 16px; line-height: 1;
}
.kebab-btn:hover { background: var(--bg3); border-color: var(--bd); color: var(--tx); }
/* ⚠️ POR ENCIMA DEL MODAL (2026-08-25). Estaba en 180 y `.modal-overlay` en 200,
   así que un ⋮ pulsado DENTRO de un modal abría su menú por detrás del propio
   modal: invisible. Y eso no se lee como «está detrás», se lee como «el botón no
   hace nada» -- que es exactamente como lo describió el usuario en el detalle
   por cuenta de un activo. Afectaba a TODOS los ⋮ dentro de un modal, no solo a
   ese. El menú es flotante: su sitio es encima de lo que lo abrió, siempre.
   Sigue por debajo de los avisos (300), que tienen que verse sobre todo. */
.row-menu {
  position: fixed; z-index: 210; min-width: 210px; padding: 5px;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius);
  box-shadow: var(--sh-2);
}
.row-menu button {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border: none; border-radius: 6px; background: none;
  color: var(--tx); font-size: var(--fs-base); text-align: left; white-space: nowrap;
}
.row-menu button:hover { background: var(--a-soft); color: var(--a2); }
/* Acción destructiva dentro del menú (borrar): antes era un botón rojo a la vista
   en la propia fila, y al meterlo en el desplegable dejaría de distinguirse de
   "Archivar" -- que es lo contrario de irreversible. */
.row-menu button.row-menu-danger { color: var(--neg); }
.row-menu button.row-menu-danger:hover { background: var(--neg-soft); color: var(--neg); }
.row-menu-icon { display: flex; flex: none; }
.row-menu-icon svg {
  width: 17px; height: 17px;
  fill: none; stroke: currentColor; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

.metric-card { display: flex; flex-direction: column; gap: 6px; }
.metric-label { font-size: var(--fs-sm); color: var(--dim); text-transform: uppercase; letter-spacing: .05em; font-weight: 600; }
/* tabular-nums fija el ancho de los dígitos: sin ello las cifras "bailan" al
   refrescarse en vivo cada 15 s, que se lee como un parpadeo de la interfaz. */
.metric-value { font-size: 26px; font-weight: 600; letter-spacing: -.4px; font-variant-numeric: tabular-nums; }
.metric-value.pos { color: var(--pos); }
.metric-value.neg { color: var(--neg); }
/* La lectura principal de la pantalla (una sola por vista, no una por tarjeta). */
.metric-value.big { font-size: var(--fs-display); font-weight: 700; letter-spacing: -.6px; }

/* Las dos gráficas de Patrimonio, a la par (2026-08-05, petición del usuario:
   antes iban a ancho completo una debajo de otra y ocupaban dos pantallas).
   `minmax(0,1fr)` es importante: sin el 0 el contenido SVG impondría su ancho
   mínimo y las columnas se desbordarían en vez de encogerse. Se apilan cuando
   media pantalla ya no da para una gráfica legible. */
.wealth-charts { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; align-items: start; }
.wealth-chart-slot { display: contents; }
@media (max-width: 1250px) { .wealth-charts { grid-template-columns: 1fr; } }

/* Panel de resumen de Patrimonio: varias lecturas juntas en un solo card */
.wealth-readings { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 18px 24px; }
.wealth-reading { display: flex; flex-direction: column; gap: 3px; }
.wealth-reading-label { font-size: var(--fs-xs); color: var(--dim); text-transform: uppercase; letter-spacing: .04em; }
.wealth-reading-value { font-size: var(--fs-md); font-weight: 600; }
.wealth-reading-value.big { font-size: 22px; font-weight: 700; }

/* Cuadro de mando: panel de resumen unificado y compacto */
.dash-panel-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 16px; margin-bottom: 14px; }
.dash-services { display: flex; flex-wrap: wrap; gap: 6px 12px; }
.dash-service { display: inline-flex; align-items: center; gap: 5px; font-size: var(--fs-xs); color: var(--dim); background: var(--bg3); border: 1px solid var(--bd); padding: 3px 8px; border-radius: 6px; }
/* ── Segundo nivel dentro de una tabla (2026-08-27) ─────────────────────────
   La fila del GRUPO es la posición; sus patas van debajo, sangradas y con un
   filete que las ata a ella. Se sangra con `padding` sobre la primera celda y no
   con una columna extra: una columna nueva descuadraría TODAS las filas, no solo
   las del grupo desplegado. */
table.data-table tr.group-row > td { font-weight: 600; }
table.data-table tr.group-row.is-open > td { background: var(--bg3); }
.grp-caret { color: var(--dim); display: inline-block; min-width: .9em; }
table.data-table tr.leg-row > td:first-child {
  padding-left: 26px; box-shadow: inset 3px 0 0 var(--bd2);
}
table.data-table tr.leg-row > td { color: var(--dim2); }
/* Una subfila que es una POSIBILIDAD, no un hecho: el trabajo que esa máquina
   PODRÍA hacer y nadie le ha asignado (2026-08-27). Atenuada a propósito -- con
   el mismo peso que las asignadas, la lista de trabajos de una máquina diría
   cosas que no son. Es la mitad de la matriz que no está en ninguna otra
   pantalla, así que no se puede quitar: se distingue. */
table.data-table tr.leg-row-off > td { opacity: .68; }
/* En móvil (tarjetas) la sangría no ata nada: la pata se marca con su filete a la
   izquierda de la tarjeta entera, igual que hace el árbol de subcuentas. */
@media (max-width: 900px) {
  table.data-table[data-mobile-cards] tr.leg-row > td:first-child { padding-left: 10px; box-shadow: none; }
  table.data-table[data-mobile-cards] tr.leg-row { border-left: 3px solid var(--bd2); }
}
/* ── OPERATIVA: tres resúmenes que hacen de pestañas (2026-08-27) ───────────
   Los tres se ven SIEMPRE (es lo que se lee de un vistazo) y solo uno enseña su
   tabla. La fila es un contenedor y el botón va dentro: el ⟳ de «cerradas hoy»
   tiene que ser HERMANO del botón, porque un <button> dentro de otro no es HTML
   válido. `role="presentation"` en la fila para que el tablist siga viendo sus
   pestañas a través del envoltorio. */
/* EN COLUMNAS, NO EN LÍNEAS (2026-08-27, lo pidió el usuario). En fila, las cuatro
   lecturas de cada bloque competían por el ancho con su título y acababan
   recortadas o en dos líneas irregulares; en columna, cada bloque es un bloquecito
   de «etiqueta ... valor» alineados, que es como se leen las cifras de al lado --
   y las tres columnas ponen los tres resúmenes a la misma altura, que es justo lo
   que se quiere comparar de un vistazo. */
.ops-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.ops-tab-row {
  display: flex; align-items: flex-start; gap: 4px;
  border: 1px solid var(--bd); border-radius: 8px; background: var(--bg3); padding-right: 6px;
}
.ops-tab-row.is-active { border-color: var(--a); background: var(--bg2); }
.ops-tab {
  flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: stretch; gap: 8px;
  background: none; border: 0; padding: 10px 12px; margin: 0;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.ops-tab:hover:not(:disabled) { background: var(--bg2); border-radius: 8px; }
.ops-tab:disabled { cursor: default; opacity: .55; }
.ops-tab-title { font-weight: 600; }
.ops-tab-caret { color: var(--dim); display: inline-block; min-width: 1em; }
/* Las lecturas, una por línea y con el valor a la derecha: alineadas se comparan
   entre columnas (la PyG de un bloque contra la del de al lado). */
.ops-tab-minis { display: flex; flex-direction: column; gap: 3px; }
.ops-mini {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: var(--fs-xs); color: var(--dim); min-width: 0;
}
.ops-mini strong { font-size: var(--fs-sm); white-space: nowrap; font-variant-numeric: tabular-nums; }
.ops-panel { margin-top: 12px; border-top: 1px solid var(--bd); padding-top: 12px; }
/* QUÉ COLUMNA ESTÁ ABIERTA, sin tener que fiarse del color: un pico que apunta al
   panel. Solo en la disposición de tres columnas -- apiladas, el panel va debajo de
   las TRES, así que un pico bajo la primera apuntaría a la segunda y no a su
   detalle. El borde y el chevrón ▾ siguen distinguiendo la activa ahí. */
@media (min-width: 1001px) {
  .ops-tab-row.is-active { position: relative; }
  .ops-tab-row.is-active::after {
    content: ''; position: absolute; left: 50%; bottom: -7px; transform: translateX(-50%);
    border-left: 7px solid transparent; border-right: 7px solid transparent;
    border-top: 7px solid var(--a);
  }
}
/* Tres columnas no caben en una pantalla estrecha: pasan a una sola, y cada bloque
   se sigue leyendo igual (etiqueta a la izquierda, valor a la derecha) porque es la
   misma forma que tienen las tablas convertidas en tarjetas. */
@media (max-width: 1000px) {
  .ops-tabs { grid-template-columns: 1fr; }
}
/* ── Mapa de DISPONIBILIDAD: una franja por máquina o proceso (2026-08-27) ──
   Hermano del mapa semanal de un proceso (`.tl-*`), pero de la FLOTA ENTERA y a
   escala continua en vez de en franjas de 5 min: aquí se comparan carriles entre
   sí, así que los tramos se dibujan donde caen y no en cubos.

   Los colores son los mismos que el mapa semanal a propósito (`--pos`, `--danger`,
   `--bd`): dos verdes distintos para «funcionando» en dos pantallas contiguas es
   como se pierde el significado de un color. */
.gantt { --gantt-name: 190px; }
.gantt-scale { position: relative; height: 16px; margin-left: var(--gantt-name); margin-bottom: 4px; }
.gantt-tick { position: absolute; top: 0; font-size: var(--fs-xs); color: var(--dim); transform: translateX(-50%); white-space: nowrap; }
.gantt-lane { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.gantt-name { width: var(--gantt-name); flex: none; font-size: var(--fs-xs); color: var(--tx); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gantt-track { position: relative; flex: 1; height: 18px; border-radius: 3px; background: var(--bg3); overflow: hidden; }
.gantt-track > i { position: absolute; top: 0; bottom: 0; }
/* La marca del cambio de día: por debajo de los tramos, para orientarse sin
   competir con lo que se viene a leer. */
.gantt-day { width: 1px; background: var(--bd); opacity: .7; }
.gt-nd { background: var(--bd); }
.gt-ok { background: var(--pos); opacity: .55; }
.gt-ko { background: var(--danger); }
.gantt-pct { width: 62px; flex: none; text-align: right; font-size: var(--fs-xs); color: var(--dim); font-variant-numeric: tabular-nums; }
@media (max-width: 700px) { .gantt { --gantt-name: 110px; } .gantt-pct { display: none; } }

/* ── Mapa de PUBLICACIONES, tipo git (2026-08-27, petición del usuario) ──────
   Una fila por versión, de la más nueva arriba, con su carril y su nodo. El
   carril es una línea continua detrás de los nodos: es lo que hace que se lea
   como una historia y no como una lista de tarjetas. */
.pkg-graph { display: flex; flex-direction: column; }
.pkg-row { display: flex; gap: 12px; }
.pkg-rail { position: relative; width: 14px; flex: none; }
/* La línea del carril. `::before` en vez de un borde para que llegue de arriba
   abajo de la fila y una fila alta (con notas y refs) no la parta. */
.pkg-rail::before { content: ''; position: absolute; left: 6px; top: 0; bottom: 0; width: 2px; background: var(--bd); }
.pkg-row:first-child .pkg-rail::before { top: 9px; }
.pkg-row:last-child .pkg-rail::before { bottom: calc(100% - 9px); }
.pkg-dot { position: absolute; left: 1px; top: 4px; width: 12px; height: 12px; border-radius: 50%; border: 2px solid var(--bg); box-sizing: border-box; }
/* Tres estados de despliegue = tres acciones distintas = tres colores. Un nodo
   HUECO para lo que está en pruebas: todavía no es un hecho para la flota. */
.pkg-dot.is-general { background: var(--pos); box-shadow: 0 0 0 2px var(--bg); }
.pkg-dot.is-canary { background: var(--bg); border-color: var(--warn); }
.pkg-dot.is-retired { background: var(--bd2); }
.pkg-body { flex: 1; padding-bottom: 14px; min-width: 0; }
.pkg-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
/* El ⋮ de la version, pegado al final de su renglon: en medio de las etiquetas de
   estado se leeria como una de ellas. `margin-left:auto` y no un hueco fijo, que
   la cabecera lleva un numero de insignias distinto en cada fila. */
.pkg-head .kebab-btn { margin-left: auto; align-self: center; }
.pkg-when, .pkg-notes { font-size: var(--fs-xs); margin-top: 2px; }
.pkg-refs { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 5px; }
/* Las REFS, como las etiquetas de rama de git: cuelgan del nodo que corren. */
.pkg-ref { font-size: var(--fs-xs); padding: 1px 7px; border-radius: 10px; border: 1px solid var(--bd2); background: var(--bg3); color: var(--tx); }
.pkg-ref.is-canary { border-color: var(--warn); }
/* Lo que YA no corre esa versión: sigue estando --es la historia-- pero no puede
   leerse igual que lo que está pasando ahora. */
.pkg-ref.is-past { opacity: .55; border-style: dashed; }

/* ── La línea de salud de una PORTADA DE ÁREA (2026-08-27) ──────────────────
   «¿Está todo bien?» es la pregunta con la que se entra, así que va ANTES de los
   bloques: si la respuesta es sí, no hay que leer nada más. Mismos umbrales y
   mismos colores que el chip del cuadro de mando -- dos verdes para «todo bien»
   en dos pantallas es como se pierde el significado de un color. */
.sys-health {
  display: flex; align-items: flex-start; gap: 10px; margin-bottom: 14px;
  background: var(--bg2); border: 1px solid var(--bd); border-left-width: 3px;
  border-radius: var(--radius-lg); padding: 12px 14px;
}
.sys-health-dot { width: 9px; height: 9px; border-radius: 50%; margin-top: 6px; flex: none; }
.sys-health.is-ok { border-left-color: var(--pos); }
.sys-health.is-ok .sys-health-dot { background: var(--pos); }
.sys-health.is-warn { border-left-color: var(--warn); }
.sys-health.is-warn .sys-health-dot { background: var(--warn); }
.sys-health.is-danger { border-left-color: var(--danger); }
.sys-health.is-danger .sys-health-dot { background: var(--danger); }
/* Cada problema es un ENLACE al sitio donde se arregla: un aviso que no dice a
   dónde ir es un aviso que se ignora. */
.sys-health-note { font-size: var(--fs-xs); color: var(--dim); margin-top: 2px; line-height: 1.8; }

/* ── Webparts: la portada de una sección (2026-08-27, petición del usuario) ──
   Una rejilla de bloques-resumen, cada uno la puerta de su subpantalla. Nace en
   Infraestructura, que era una columna de ocho tarjetas donde ninguna se veía
   entera; el patrón vale para cualquier sección que crezca igual.

   ES EL MISMO LENGUAJE QUE LAS TARJETAS DE VÍA DE PROGRESO (`.path-card`), y no
   una tarjeta de contenido (`.card`) con un clic encima: aquello es exactamente
   esto --un bloque que resume y en el que se entra--, así que compartirlo es lo
   que hace que se entienda sin explicarlo. Icono a la izquierda como el anillo de
   una vía, el nombre en grande y UNA lectura debajo.

   El bloque entero es la puerta, con `role="button"` y `tabindex` en el marcado:
   un div que navega y no admite teclado es una trampa (§16). */
.webparts { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.webpart {
  position: relative; display: flex; gap: 12px; align-items: flex-start;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius-lg);
  padding: 14px 14px 14px 14px; cursor: pointer;
  transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}
.webpart:hover { box-shadow: var(--sh-2); border-color: var(--bd2); transform: translateY(-1px); }
.webpart:focus-visible { outline: 2px solid var(--a); outline-offset: 2px; }
.webpart-body { min-width: 0; flex: 1; }
.webpart-name { font-weight: 700; font-size: var(--fs-md); letter-spacing: -.2px; }
/* LA LECTURA: la respuesta y nada más. La descripción de lo que hay dentro se fue
   al `title` del bloque -- ocupaba la línea más visible para repetir el título. */
.webpart-read { font-size: var(--fs-sm); color: var(--dim2); margin-top: 5px; line-height: 1.6; }
.webpart-read strong { color: var(--tx); font-size: var(--fs-md); }
.webpart-icon { flex: none; width: 34px; height: 34px; display: grid; place-items: center;
  border-radius: 9px; background: var(--bg3); color: var(--a2); }
.webpart-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
/* El ⋮ va en la ESQUINA. En la fila del título se quedaba flotando en medio del
   bloque, donde no parece de nadie. Y no navega: el JS para la propagación, pero
   además tiene que estar por encima para poder pulsarlo. */
.webpart-menu { position: absolute; top: 8px; right: 8px; z-index: 1; }
/* La flecha dice «esto lleva a algún sitio» sin ocupar una línea de texto. Se
   despierta al pasar por encima: permanente competiría con la cifra, que es lo
   que se viene a leer. */
.webpart-go { position: absolute; right: 12px; bottom: 10px; color: var(--dim);
  opacity: .4; transition: opacity .15s, transform .15s; }
.webpart:hover .webpart-go { opacity: 1; transform: translateX(2px); color: var(--a2); }
@media (max-width: 700px) { .webparts { grid-template-columns: 1fr; } }

/* El webpart de salud de los procesos: la respuesta a «¿está todo corriendo?» de
   un vistazo, y el detalle a un clic. Sustituye a la tira de N pastillas. */
.dash-health {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-xs); color: var(--dim); cursor: pointer;
  background: var(--bg3); border: 1px solid var(--bd); border-radius: 6px; padding: 4px 10px;
}
.dash-health:hover { background: var(--bg2); }
.dash-health strong { font-size: var(--fs-sm); color: var(--tx); }
.dash-health.is-warn { border-color: var(--warn); }
.dash-health.is-danger { border-color: var(--danger); }
.dash-health-note { color: var(--dim); }
@media (max-width: 700px) { .dash-health-note { display: none; } }
.dash-groups { display: flex; flex-wrap: wrap; gap: 16px 28px; }
.dash-group { display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.dash-group-title { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .04em; color: var(--dim); font-weight: 600; }
.dash-group-readings { display: flex; flex-wrap: wrap; gap: 12px 22px; }
.dash-reading { display: flex; flex-direction: column; gap: 2px; }
.dash-reading-label { font-size: var(--fs-xs); color: var(--dim); }
.dash-reading-value { font-size: var(--fs-lg); font-weight: 600; white-space: nowrap; font-variant-numeric: tabular-nums; }
.dash-reading-value.pos { color: var(--pos); }
.dash-reading-value.neg { color: var(--neg); }

table.data-table { width: 100%; border-collapse: collapse; font-size: var(--fs-nm); }
/* Cabecera fija: estas tablas llegan a cientos de filas (operaciones), y sin
   esto se pierde de vista qué columna es cada una al bajar. */
table.data-table th {
  position: sticky; top: 0; z-index: 1;
  text-align: left; padding: 9px 10px; font-size: var(--fs-sm); font-weight: 600; color: var(--tx);
  background: var(--a-soft); border-bottom: 1px solid var(--bd);
}
table.data-table td { padding: 9px 10px; border-bottom: 1px solid var(--bd); }
/* Zebra muy tenue: ayuda a seguir la fila en tablas anchas (la de Cuentas tiene
   ~20 columnas) sin ensuciar el diseño. El hover y la fila activa van por
   encima, así que siguen ganando. */
table.data-table tbody tr:nth-child(even) td { background: var(--zebra); }
table.data-table tr:hover td { background: var(--bg3); }
table.data-table tr.clickable { cursor: pointer; }
table.data-table tr.clickable.active td { background: var(--a-soft); }
/* Fila de totales al pie de una tabla. */
table.data-table tr.row-total td { font-weight: 700; border-top: 2px solid var(--bd2); background: var(--bg3); }

/* ── Jerarquía de subcuentas: un ÁRBOL de verdad (2026-08-13) ───────────────
   La sangría eran los caracteres «└─» delante del nombre. Escrito con texto, el
   elbow se copia al portapapeles con el nombre de la cuenta, un lector de
   pantalla lo lee como si fuera parte del dato y su ancho baila con la
   tipografía. Aquí se DIBUJA: una guía vertical que baja desde la principal y
   una marca corta hacia el nombre, que es como se lee un árbol de un vistazo.
   La guía se corta en la última hermana (sin `.tree-mid`) y sigue hacia abajo en
   las demás -- ese detalle es justo lo que dice "aún queda grupo por debajo".
   Ojo: la línea de las intermedias tiene que cruzar el borde de la fila, así que
   se estira más abajo que la celda a propósito. */
.tree-child { position: relative; display: inline-block; padding-left: 20px; }
.tree-child::before {   /* guía vertical, desde el borde superior de la fila */
  content: ''; position: absolute; left: 6px; top: -11px; height: 20px;
  border-left: 1px solid var(--bd2);
}
.tree-child::after {    /* la marca corta que entra en el nombre */
  content: ''; position: absolute; left: 6px; top: 9px; width: 8px;
  border-top: 1px solid var(--bd2);
}
.tree-child.tree-mid::before { height: 40px; }   /* no es la última: la guía continúa */
/* Subcuenta que se ve porque su PRINCIPAL pasa el filtro, no porque lo pase ella
   (el árbol no se rompe nunca). Atenuada: sigue siendo información, pero no es
   lo que se ha pedido ver. No se baja más de .66 -- por debajo deja de leerse el
   signo de una cifra, y aquí hay dinero. */
table.data-table tr.row-off-filter td { opacity: .66; }
table.data-table tr.row-off-filter:hover td { opacity: 1; }
/* La fila de detalle es SOLO del modo tabla en móvil (ver App._detailRowFor). Se
   oculta aquí, en la base, y la media query la enseña: si alguien despliega una
   fila y ensancha la ventana, en escritorio no debe quedar una fila a todo lo
   ancho repitiendo columnas que ya están visibles. */
table.data-table tr.row-detail { display: none; }

/* Clasificar operaciones en bloque (v9 I2.3). La barra aparece SOLO con algo
   seleccionado: fija con "0 seleccionadas" seria ruido permanente sobre la tabla. */
.cer-bulk {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  padding: 8px 10px; margin-bottom: 10px;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius);
}
.cer-bulk[hidden] { display: none; }   /* el display:flex gana al atributo si no se dice */
.cer-bulk > span { font-weight: 600; }
/* La columna de la casilla no debe robar ancho a los datos. */
table.data-table th.cer-pick, table.data-table td.cer-pick { width: 28px; text-align: center; }

/* Columnas numéricas/de importe (2026-07-29, petición del usuario: mejor
   presentación visual -- alinear a la derecha para poder comparar cifras de
   un vistazo, en vez de que floten pegadas a la izquierda). tabular-nums
   evita que los dígitos "bailen" de anchura entre filas. */
table.data-table th.num, table.data-table td.num { text-align: right; font-variant-numeric: tabular-nums; }

/* ── Calendario de PnL diario ────────────────────────────────────────────────
   La plantilla de columnas vive AQUÍ y no en un style inline (2026-08-05): con
   el inline no había forma de redefinirla en móvil.
   `minmax(0,1fr)` en vez de `1fr` es la corrección de un desbordamiento real:
   `1fr` equivale a `minmax(auto,1fr)`, así que una columna NO puede encogerse por
   debajo del contenido de su celda -- en pantalla estrecha, los días con importe
   ("-112.01 USD") se quedaban con su ancho mínimo y comían el hueco de los demás,
   que salían aplastados. Con min 0 las columnas se reparten a partes iguales y es
   el texto el que se recorta. */
.cal-grid7 { display: grid; grid-template-columns: 30px repeat(7, minmax(0, 1fr)) minmax(104px, 1.4fr); gap: 6px; }
.cal-head { font-size: var(--fs-xs); color: var(--dim); text-align: center; margin-bottom: 4px; }
.cal-day { min-height: 64px; border: 1px solid var(--bd); border-radius: 6px; padding: 6px; overflow: hidden; }
.cal-day.clickable { cursor: pointer; }
.cal-day.clickable:hover { border-color: var(--bd2); box-shadow: 0 0 0 1px var(--bd2) inset; }
.cal-daynum { font-size: var(--fs-xs); color: var(--dim); }
.cal-pnl { font-size: var(--fs-sm); font-weight: 700; white-space: nowrap; }
.cal-cur { font-weight: 400; font-size: var(--fs-xs); color: var(--dim); }
.cal-ops { font-size: var(--fs-xs); color: var(--dim); white-space: nowrap; }
/* Nº de semana y resumen semanal. Estos estilos ESTABAN INLINE, y por eso la
   media query del móvil no conseguía ocultarlos (un `style="display:flex"` gana a
   cualquier regla de hoja de estilos): las dos celdas se colaban en las columnas
   de los días y descuadraban la semana entera. */
.cal-weeknum { display: flex; align-items: center; justify-content: center; color: var(--dim); font-size: var(--fs-xs); }
.cal-week {
  display: flex; flex-direction: column; justify-content: center; gap: 2px;
  padding: 4px; font-size: var(--fs-xs); border-left: 2px solid var(--bd);
}
.cal-head > .cal-week { border-left: none; align-items: center; }
.cal-week-pnl { font-weight: 700; white-space: nowrap; }
.cal-week-ops { color: var(--dim); }
.cal-ops-open { color: var(--warn); font-weight: 600; }
.cal-live { font-size: var(--fs-xs); color: var(--pos); font-weight: 600; white-space: nowrap; }

.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-size: var(--fs-sm); font-weight: 500; color: var(--dim); }
.field input, .field select, .field textarea { height: 34px; padding: 0 10px; background: var(--input-bg); border: 1px solid var(--bd2); border-radius: var(--radius); color: var(--tx); }
.field textarea { height: auto; padding: 8px 10px; resize: vertical; font-family: monospace; font-size: var(--fs-sm); }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--a); box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
/* Fila de campos de un formulario. flex-wrap + min-width son los dos a la vez la
   corrección de un desbordamiento real (2026-08-05): un flex item no baja de su
   ancho mínimo de contenido, así que al llegar la fila a 4 campos los <select>
   se negaban a encogerse y sacaban SCROLL HORIZONTAL en el modal de la cuenta
   -- los últimos campos quedaban fuera de la vista y no se podían ni ver ni
   rellenar. Con min-width los campos no bajan de 150px y, si no caben, la fila
   se parte en dos en vez de desbordar. */
.fields-row { display: flex; gap: 12px; flex-wrap: wrap; }
.fields-row .field { flex: 1 1 0; min-width: 150px; }
/* Modal de formulario ancho (ficha de la cuenta): 520px daban para 3 campos por
   fila; con las reglas del reto y el fondeo de la subcuenta hacen falta más.
   Doble clase a propósito: `.modal` declara width:520px MÁS ABAJO en este mismo
   fichero y con un solo `.modal-wide` (misma especificidad) ganaría el orden. */
.modal.modal-wide { width: 760px; }

.btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; height: 34px; padding: 0 16px; border-radius: var(--radius); border: 1px solid transparent; font-weight: 600; font-size: var(--fs-sm); }
.btn-primary { background: var(--a); color: #fff; }
.btn-primary:hover { background: var(--a2); }
.btn-secondary { background: var(--bg2); border-color: var(--bd2); color: var(--tx); }
.btn-secondary:hover { background: var(--bg3); }
.btn-danger { background: var(--danger); color: #fff; }
.btn-sm { height: 27px; padding: 0 11px; font-size: var(--fs-xs); }

/* Pills de estado (estilo panel Overa: pastel suave con borde del mismo tono,
   esquinas poco redondeadas -- no cápsulas). */
.badge { display: inline-block; padding: 2px 9px; border-radius: 6px; border: 1px solid transparent; font-size: var(--fs-sm); font-weight: 500; white-space: nowrap; }
.badge-ok { background: var(--ok-soft); border-color: var(--ok-bd); color: var(--ok-tx); }
.badge-warn { background: var(--warn-soft); border-color: var(--warn-bd); color: var(--warn-tx); }
.badge-danger { background: var(--danger-soft); border-color: var(--danger-bd); color: var(--danger-tx); }
.badge-info { background: var(--info-soft); border-color: var(--info-bd); color: var(--info-tx); }
.badge-neutral { background: var(--bg3); border-color: var(--bd); color: var(--dim); }

/* Aclaración al lado del título de una tarjeta ("— de dónde salen las señales
   que se copian"). Estaba repetida como estilo inline en media docena de
   pantallas; es la misma frase visual en todas. */
.card-title-note { font-weight: 400; font-size: var(--fs-xs); color: var(--dim); }
/* Fila elegida de una tabla que hace de índice (la fuente cuya matriz se está
   viendo). No es un estado del dato, es dónde está el usuario. */
.row-selected > td { background: var(--a-soft); }
/* Casilla + texto en una celda, alineados y con el texto pulsable. */
.switch-label { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }

.modal-overlay { position: fixed; inset: 0; background: rgba(15,23,42,.45); display: none; align-items: center; justify-content: center; z-index: 200; }
.modal-overlay.open { display: flex; }
.modal { background: var(--modal-bg); border-radius: var(--radius-lg); padding: 24px; width: 520px; max-width: 94vw; max-height: 90vh; overflow-y: auto; box-shadow: 0 20px 60px rgba(15,23,42,.2); display: flex; flex-direction: column; gap: 14px; }
.modal-title { font-size: var(--fs-md); font-weight: 700; }
.modal-actions { display: flex; gap: 8px; justify-content: flex-end; }

#toast-container { position: fixed; bottom: 24px; right: 24px; display: flex; flex-direction: column; gap: 8px; z-index: 300; }
.toast { background: var(--toast-bg); border: 1px solid var(--bd2); border-radius: var(--radius); padding: 10px 16px; font-size: var(--fs-sm); min-width: 220px; box-shadow: 0 4px 20px var(--shadow); }
.toast.ok { border-left: 3px solid var(--ok); }
.toast.err { border-left: 3px solid var(--danger); }

.empty-state { padding: 40px 20px; text-align: center; color: var(--dim); font-size: var(--fs-sm); }

/* ── Primer uso (2026-08-05) ───────────────────────────────────────────────
   Sustituye al cuadro de ceros que se veía sin ninguna cuenta dada de alta.
   Los pasos van en 3 columnas en escritorio y apilados en móvil. */
.welcome { padding: 28px 26px; }
.welcome-head { max-width: 680px; }
.welcome-title { font-size: 21px; font-weight: 700; letter-spacing: -.3px; }
.welcome-sub { margin-top: 7px; color: var(--dim); font-size: var(--fs-md); line-height: 1.5; }
.welcome-steps {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 14px;
  margin-top: 24px; padding-top: 22px; border-top: 1px solid var(--bd);
}
@media (max-width: 900px) { .welcome-steps { grid-template-columns: 1fr; } }
.welcome-step { display: flex; gap: 12px; align-items: flex-start; }
/* El número lleva el acento de marca: marca el orden sin necesitar una flecha. */
.welcome-step-n {
  display: flex; align-items: center; justify-content: center; flex: none;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--a-soft); color: var(--a2); font-size: var(--fs-sm); font-weight: 700;
}
.welcome-step-body { min-width: 0; }
.welcome-step-title { font-size: var(--fs-md); font-weight: 600; }
.welcome-step-desc { margin: 5px 0 11px; color: var(--dim); font-size: var(--fs-nm); line-height: 1.5; }

/* ── Carga: spinner y estado "ocupado" ────────────────────────────────────
   (2026-08-05, petición del usuario: el cuadro de mando y Patrimonio tardan y
   solo salía el texto "Cargando…", que no se distingue de una pantalla en
   blanco.) Dos situaciones distintas y deliberadamente distintas a la vista:

   1. PRIMERA carga, aún no hay nada que mostrar -> `.loading-block`: spinner
      centrado con su rótulo, ocupando el hueco del panel.
   2. RECARGA de algo que ya está en pantalla -> `.is-busy` en el contenedor:
      atenúa lo que hay y superpone un spinner. Nunca se vacía el panel, para
      que la cifra anterior siga legible mientras llega la nueva (y para que la
      página no "salte" al desaparecer y reaparecer el contenido). */
.spinner {
  display: inline-block; width: 16px; height: 16px; flex: none;
  border: 2px solid var(--bd2); border-top-color: var(--a);
  border-radius: 50%; animation: ti-spin .7s linear infinite;
}
.spinner-lg { width: 26px; height: 26px; border-width: 3px; }
@keyframes ti-spin { to { transform: rotate(360deg); } }
/* Respeta a quien pide menos animación: se deja de girar y se marca el avance
   con un pulso de opacidad, que no produce sensación de movimiento. */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: ti-pulse 1.2s ease-in-out infinite; }
  @keyframes ti-pulse { 0%,100% { opacity: 1; } 50% { opacity: .35; } }
}

.loading-block {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 10px;
  padding: 40px 20px; color: var(--dim); font-size: var(--fs-sm);
}

/* Estado de error de una pantalla: mensaje en lenguaje normal + reintentar, con
   el detalle técnico plegado (le sirve a soporte, no al usuario). Antes se
   pintaba el mensaje crudo del servidor y no había forma de reintentar. */
.error-state {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 44px 20px; text-align: center;
}
.error-state-icon {
  display: flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; border-radius: 50%;
  background: var(--neg-soft); color: var(--neg); font-size: 19px; font-weight: 700;
}
.error-state-msg { color: var(--tx); font-size: var(--fs-md); max-width: 460px; line-height: 1.5; }
.error-state-detail { font-size: var(--fs-xs); color: var(--dim); max-width: 560px; }
.error-state-detail summary { cursor: pointer; }
.error-state-detail code {
  display: block; margin-top: 7px; padding: 8px 10px; text-align: left;
  background: var(--bg3); border-radius: 6px; font-family: monospace; word-break: break-word;
}

/* El contenedor necesita ser position:relative para anclar el velo; se aplica
   solo en estado ocupado para no alterar el apilamiento el resto del tiempo. */
.is-busy { position: relative; }
.is-busy > *:not(.busy-veil) { opacity: .45; transition: opacity .12s; pointer-events: none; }
.busy-veil {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  border-radius: inherit;
}
/* El spinner va sobre una pastilla opaca: encima de una tabla llena de números
   un spinner suelto se pierde. */
.busy-veil > span {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 13px; border-radius: 20px;
  background: var(--bg2); border: 1px solid var(--bd);
  box-shadow: var(--sh-2); font-size: var(--fs-sm); color: var(--dim); font-weight: 500;
}

/* Botón de recargar un panel por separado. Discreto en reposo (no debe competir
   con los datos) y con el icono girando mientras trae los suyos. */
.panel-reload {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; flex: none;
  border: 1px solid transparent; border-radius: 6px;
  background: none; color: var(--dim2); font-size: 13px; line-height: 1;
}
.panel-reload:hover { background: var(--bg3); border-color: var(--bd); color: var(--tx); }
.panel-reload:disabled { cursor: default; color: var(--dim2); background: none; border-color: transparent; }
.panel-reload.spinning { animation: ti-spin .7s linear infinite; }
@media (prefers-reduced-motion: reduce) { .panel-reload.spinning { animation: ti-pulse 1.2s ease-in-out infinite; } }
.dropzone { border: 1px dashed var(--bd2); border-radius: var(--radius); padding: 32px; text-align: center; cursor: pointer; background: var(--bg); }
.dropzone:hover, .dropzone.dragover { border-color: var(--a); background: var(--a-soft); }
.dropzone input[type=file] { display: none; }
.hidden { display: none !important; }

::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-thumb { background: var(--scroll-c); border-radius: 3px; }

/* ── Móvil (2026-07-28, petición del usuario) ──────────────────────────────
   El sidebar fijo de escritorio no cabe en una pantalla de móvil: por debajo
   de 760px pasa a ser un cajón oculto (position:fixed, fuera de pantalla vía
   transform) que se despliega con el botón hamburguesa del topbar y se cierra
   solo al elegir una pestaña o tocar el fondo oscurecido -- ver app.js
   openSidebar/closeSidebar. El resto de reglas de aquí abajo son ajustes
   generales (grids a 1 columna, formularios apilados, tablas con scroll
   horizontal propio) para que las pantallas ya existentes no se rompan en
   una pantalla estrecha sin tener que rehacer cada una a mano. */
/* 6 columnas (estadísticas por periodo: hoy/semana/mes/trimestre/año/win rate)
   se aprietan demasiado antes de llegar al colapso a 1 columna de abajo --
   paso intermedio a 3 columnas (2 filas) en pantallas medianas/tablet. */
@media (max-width: 760px) {
  .menu-toggle { display: flex; }
  /* Instalada como aplicación (display:standalone) la barra superior llega hasta
     el borde de la pantalla: en un móvil con notch hay que respetar el recorte, o
     el logo y los botones quedan debajo. Fuera del modo instalado el inset es 0,
     así que la regla no cambia nada en el navegador normal. */
  .topbar { padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right)); }
  /* La barra son 48px con el hamburguesa, el logo y cuatro iconos: el campo de
     busqueda se queda en su lupa. El texto y la tecla no aportan nada donde no
     hay teclado. */
  .topbar-search { min-width: 0; padding: 0; border: none; background: none; width: 44px; height: 44px; justify-content: center; }
  .topbar-search-txt, .topbar-search-kbd { display: none; }
  /* El nombre largo de la marca sobra cuando el sitio hace falta para el resto:
     la marca sigue identificando la aplicacion. */
  .topbar-wordmark { display: none; }

  .sidebar {
    position: fixed; top: var(--nav-h); left: 0; bottom: 0; z-index: 150;
    transform: translateX(-100%); transition: transform .2s ease;
    box-shadow: 2px 0 24px rgba(0,0,0,.2);
  }
  .sidebar.open { transform: translateX(0); }
  /* El cajón móvil es una superposición temporal, no un panel fijo que ocupe
     sitio -- el modo "solo iconos" (pensado para ahorrar ancho permanente en
     escritorio) no aporta nada aquí, así que se ignora aunque esté activado. */
  .sidebar:is(.collapsed, .rail-auto) { width: var(--side-w); }
  .sidebar:is(.collapsed, .rail-auto) .sidebar-item { justify-content: flex-start; padding: 7px 16px; gap: 8px; }
  .sidebar:is(.collapsed, .rail-auto) .sidebar-label { display: inline; }
  /* Se ignora `collapsed` también en el menú de dos niveles. El cajón móvil es
     ancho y lleva texto, así que aquí no vale el tratamiento del menú compacto
     de escritorio (iconos centrados sobre una banda gris): un móvil que trajera
     el modo compacto guardado de escritorio se quedaría con una tira de iconos
     sin nombre y con la sangría perdida. */
  .sidebar:is(.collapsed, .rail-auto) .nav-group.open .nav-children { display: block; }
  .sidebar:is(.collapsed, .rail-auto) .nav-caret { display: block; }
  .sidebar:is(.collapsed, .rail-auto) .nav-children {
    margin: 2px 0 4px 18px; padding-left: 10px;
    border-left: 1px solid var(--bd); background: none; border-radius: 0;
  }
  .sidebar:is(.collapsed, .rail-auto) .nav-child { justify-content: flex-start; padding: 7px 10px; gap: 9px; }
  .sidebar:is(.collapsed, .rail-auto) .nav-child .sidebar-icon svg { width: 18px; height: 18px; }
  .sidebar:is(.collapsed, .rail-auto) .nav-child.active::before { display: none; }
  /* El tercer nivel también se ignora `collapsed` en el cajón móvil: aquí hay
     texto y sitio, así que esconderlo dejaría las subpantallas fuera del menú. */
  .sidebar:is(.collapsed, .rail-auto) .nav-grandchildren.open { display: block; }
  .sidebar:is(.collapsed, .rail-auto) .nav-grandchild { justify-content: flex-start; padding: 6px 10px; }
  .sidebar:is(.collapsed, .rail-auto) .nav-parent.in-path { background: none; }
  .sidebar:is(.collapsed, .rail-auto) .nav-parent.in-path::before { left: 0; }
  .sidebar-collapse-btn { display: none; }
  /* La miga de pan en móvil: sin el padding de escritorio y sin que el icono del
     área se coma el ancho útil del último tramo, que es el que importa. */
  .breadcrumb { padding: 5px 16px; min-height: 38px; gap: 5px; }
  .sidebar-backdrop.open {
    display: block; position: fixed; top: var(--nav-h); left: 0; right: 0; bottom: 0;
    background: rgba(0,0,0,.4); z-index: 140;
  }

  .screen { padding: 14px; gap: 12px; }
  .card { padding: 14px; }
  .page-header { flex-wrap: wrap; row-gap: 8px; }

  .metric-value { font-size: 19px; }

  /* Filas de campos de formulario -- una columna por fila en vez de apretarlas
     una al lado de otra. Las filas que son solo botones (pestañas, filtros de
     cartera) no tienen ningún .field dentro, así que en vez de apilarse se
     dejan hacer flex-wrap -- se ven como botones que pasan a la siguiente línea. */
  .fields-row:has(.field) { flex-direction: column; }
  .fields-row:not(:has(.field)) { flex-wrap: wrap; }

  /* ── Tablas como TARJETAS (2026-08-05, petición del usuario) ───────────────
     Antes se dejaba que la tabla hiciera scroll horizontal. Con tablas de más de
     20 columnas (la de cuentas) eso significa arrastrar de lado varias pantallas
     para leer una fila: inservible en el móvil. Ahora cada fila es una tarjeta y
     cada celda una línea "Cabecera .......... valor".
     El nombre de la columna sale de `data-label`, que rellena App.labelTables()
     leyendo el <thead> -- así vale para TODAS las tablas de la aplicación sin
     tocar las decenas de sitios que las pintan. */
  table.data-table { display: block; }
  table.data-table thead { display: none; }         /* la cabecera pasa a cada celda */
  table.data-table tbody, table.data-table td { display: block; }
  /* flex-column en vez de block: se ve igual (celdas apiladas) pero permite subir
     la celda marcada como título con `order`, aunque en la tabla vaya la cuarta. */
  table.data-table tr {
    display: flex; flex-direction: column;
    border: 1px solid var(--bd); border-radius: var(--radius); padding: 8px 10px;
    margin-bottom: 8px; background: var(--bg2);
  }
  table.data-table tr:hover { background: var(--bg2); }  /* el hover de fila no aporta nada al tacto */
  /* `flex-end` + `margin-right:auto` en la etiqueta, en vez de space-between: una
     celda puede tener VARIOS hijos (importe + pill "Real"/"Fondeo"), y con
     space-between se repartían por todo el ancho dejando el importe suelto a la
     izquierda y la pill al otro extremo. Así la etiqueta queda a la izquierda y
     todo el valor junto a la derecha. */
  table.data-table td {
    display: flex; justify-content: flex-end; align-items: baseline; gap: 8px;
    border: none; padding: 3px 0; text-align: left !important; white-space: normal;
  }
  /* La cabecera de la columna, delante del valor. Se omite en las celdas sin
     nombre (la del menú ⋮) y en las que ocupan varias columnas (totales). */
  table.data-table td[data-label]:not([data-label=""])::before {
    content: attr(data-label); flex: 0 1 auto; margin-right: auto; padding-right: 10px;
    color: var(--dim); font-size: var(--fs-xs); font-weight: 500;
  }
  table.data-table td.num { font-variant-numeric: tabular-nums; }
  /* Una celda vacía no merece una línea entera en la tarjeta. */
  table.data-table td:empty { display: none; }
  table.data-table tr.row-total { background: var(--bg3); font-weight: 700; }
  /* La primera celda hace de TÍTULO de la tarjeta, así que va sola y a la
     izquierda: repetir su etiqueta ("Nombre: Bybit (Exchange)") es ruido, se
     entiende sin ella. Si la tabla marca otra columna con `data-card-title`
     (p.ej. el símbolo en una tabla ordenada por fecha), manda esa y sube al
     principio de la tarjeta con `order`. */
  table.data-table tbody tr:not(:has(> td[data-card-title])) > td:first-child,
  table.data-table tbody tr > td[data-card-title] {
    font-weight: 600; font-size: var(--fs-md); justify-content: flex-start;
    padding-bottom: 6px; margin-bottom: 4px; border-bottom: 1px solid var(--bd);
  }
  table.data-table tbody tr:not(:has(> td[data-card-title])) > td:first-child::before,
  table.data-table tbody tr > td[data-card-title]::before { display: none; }
  table.data-table tbody tr > td[data-card-title] { order: -1; }
  /* Los contenedores con scroll horizontal ya no tienen nada que desplazar. */
  .table-scroll { overflow-x: visible; }

  /* El árbol de subcuentas, en modo tarjeta: cada fila es una TARJETA suelta,
     así que una guía que baja de la tarjeta de arriba no une nada -- quedaría un
     trazo huérfano sobre el título. Se cambia por un filete a la izquierda del
     nombre, que es la misma idea (esto cuelga de lo anterior) sin fingir una
     línea continua entre cajas separadas. */
  .tree-child { padding-left: 9px; border-left: 2px solid var(--bd2); }
  .tree-child::before, .tree-child::after { display: none; }

  /* ── Agrupar y resumir la tarjeta (2026-08-06, petición del usuario) ───────
     Con 12 columnas la tarjeta era la tabla girada: 12 líneas por operación. Dos
     marcas en el <th> (ver App.labelTables) lo resuelven:
       - `data-merge-prev`: el valor se enseña dentro de la celda anterior entre
         paréntesis ("SL 194.615 (41.3 pips)") y su propia línea desaparece.
       - `data-prio="2"`: secundario, oculto hasta pulsar "Ver más".
     En escritorio ninguna de las dos hace nada: la tabla sigue teniendo todas sus
     columnas, que es donde comparar cifras alineadas sí compensa. */
  table.data-table td.merged-away { display: none; }
  table.data-table td .cell-extra { display: inline; color: var(--dim); font-size: var(--fs-xs); }
  table.data-table tbody tr:not(.card-expanded) td[data-prio="2"] { display: none; }
  /* El desplegable vive en la cabecera de la tarjeta: un chevrón a la derecha del
     símbolo, y se pulsa toda esa línea. Antes era un botón "Ver más" a todo lo
     ancho al pie de cada tarjeta, que pesaba tanto como la propia operación --
     en una lista de 20 se leían 20 botones antes que ningún dato. */
  table.data-table tbody tr[data-card-toggle] > td[data-card-title],
  table.data-table tbody tr[data-card-toggle]:not(:has(> td[data-card-title])) > td:first-child {
    cursor: pointer;
  }
  .card-more-btn {
    display: inline-flex; align-items: center; justify-content: center;
    /* margin-left:auto lo empuja al extremo derecho de la línea del título, que
       es flex con justify-content:flex-start. */
    margin-left: auto; padding: 2px; width: 28px; height: 28px;
    border: none; background: none; color: var(--dim); cursor: pointer;
  }
  .card-more-btn svg { transition: transform .15s ease; }
  tr.card-expanded .card-more-btn svg { transform: rotate(180deg); }
  tr.card-expanded .card-more-btn { color: var(--a2); }

  /* ── Nivel 3: fuera del móvil (2026-08-09) ─────────────────────────────────
     No es "secundario", es que en un móvil no aporta nada (apalancamiento, tipo
     en el bróker, umbral, origen…). Sin este nivel, repartir 23 columnas entre
     esencial y secundario dejaba un desplegable de 18 líneas. Se oculta la celda
     Y su cabecera: en modo tabla, una th sin sus td descuadraría la rejilla. */
  table.data-table td[data-prio="3"],
  table.data-table th[data-prio="3"] { display: none !important; }

  /* ── Modo TABLA en móvil (2026-08-09, petición del usuario) ────────────────
     "En trades activos y cerrados, en vez de tarjetas, podría ser tabla". Una
     operación no se consulta de una en una: se COMPARA con las de al lado, y la
     tarjeta rompe la alineación vertical, que es lo único que hace legible una
     columna de cifras. Aquí la tabla SIGUE siendo tabla y lo que se recorta son
     las columnas: a 12px en 360px caben ~3 o 4, así que elegir este modo obliga
     a elegir tres cifras (ver docs/REVISION-MOVIL.md §2).

     Deshace, solo para estas tablas, el volcado a tarjeta de arriba. */
  /* `width:100%` con reparto AUTOMÁTICO (no `table-layout:fixed`): la tabla no
     puede exceder su contenedor, así que las columnas se estrechan y el texto se
     parte en vez de desbordar. Con `nowrap` y cuatro columnas parecía caber, pero
     una fecha completa más un importe con divisa ya se salían -- y desbordar aquí
     significa devolver el scroll horizontal que este modo existe para evitar. */
  table.data-table[data-mobile="table"] {
    display: table; width: 100%; font-size: var(--fs-xs);
  }
  table.data-table[data-mobile="table"] thead { display: table-header-group; }
  table.data-table[data-mobile="table"] tbody { display: table-row-group; }
  table.data-table[data-mobile="table"] tr {
    display: table-row; border: none; border-radius: 0; padding: 0;
    margin-bottom: 0; background: none;
  }
  table.data-table[data-mobile="table"] th,
  table.data-table[data-mobile="table"] td {
    display: table-cell; padding: 6px 4px; white-space: normal;
    border-bottom: 1px solid var(--bd); vertical-align: top;
  }
  /* La etiqueta delante del valor es de la tarjeta: aquí ya está en la cabecera,
     y repetirla en cada celda es justo lo que hacía ilegible el móvil. */
  table.data-table[data-mobile="table"] td::before { content: none !important; }
  /* Lo secundario no se ve, y tampoco su cabecera (o quedaría una columna vacía). */
  table.data-table[data-mobile="table"] td[data-prio="2"],
  table.data-table[data-mobile="table"] th[data-prio="2"] { display: none; }
  /* Una celda vacía SÍ ocupa su hueco aquí: es una columna, no una línea de texto
     que sobre. La regla `td:empty { display:none }` de la tarjeta la descuadraría. */
  table.data-table[data-mobile="table"] td:empty { display: table-cell; }
  table.data-table[data-mobile="table"] td.num { text-align: right !important; }
  /* El título de la tarjeta vuelve a ser una celda normal: en una tabla ya está en
     su columna, y `order:-1` no tiene sentido dentro de una fila de tabla. */
  table.data-table[data-mobile="table"] td[data-card-title] {
    font-weight: 600; font-size: var(--fs-xs); order: 0;
    margin-bottom: 0; padding-bottom: 6px; border-bottom: 1px solid var(--bd);
  }
  table.data-table[data-mobile="table"] .card-more-btn {
    margin-left: 4px; width: 20px; height: 20px; padding: 0; vertical-align: middle;
  }
  /* Fila de detalle: lo secundario, debajo y a lo ancho. Revelar la celda oculta
     habría añadido una columna a TODAS las filas, no solo a la desplegada. */
  table.data-table[data-mobile="table"] tr.row-detail { display: table-row; }
  table.data-table th.cer-pick, table.data-table td.cer-pick { display: none; }
  .cer-bulk { display: none; }
  table.data-table[data-mobile="table"] tr.row-detail > td {
    white-space: normal; background: var(--bg2); padding: 6px 8px;
  }
  .row-detail-item { display: flex; justify-content: space-between; gap: 10px; padding: 2px 0; }
  .row-detail-label { color: var(--dim); }
  .row-detail-val { text-align: right; font-variant-numeric: tabular-nums; }

  .modal { padding: 16px; } /* max-width:94vw ya limita el ancho a cualquier tamaño de pantalla, ver regla base */

  /* Paneles de "maestro/detalle" (lista de trades + su ficha al lado) --
     accounts.js y account-analytics.js les añaden esta clase a propósito. */
  .split-view { flex-direction: column; }
  .split-view > * { flex: none !important; max-height: none !important; }

  /* ── Calendario de PnL diario en móvil ─────────────────────────────────────
     Se mantiene la forma de calendario (colapsarlo a una columna le quitaría el
     sentido), pero se quitan las dos columnas que no caben: el nº de semana y el
     resumen semanal. Con ellas, los 7 días se repartían ~28px cada uno y los que
     tenían importe deformaban la fila entera. El resumen del mes sigue arriba en
     su badge, así que no se pierde información esencial.
     El importe se queda; lo que se va es el sufijo "USD" de cada celda (la propia
     tarjeta ya dice «importes en USD») y el recuento de operaciones. */
  .cal-grid7 { grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 3px; }
  .cal-grid7 > .cal-weeknum, .cal-grid7 > .cal-week { display: none; }
  .cal-day { min-height: 46px; padding: 3px; }
  .cal-pnl { font-size: var(--fs-xs); }
  .cal-cur, .cal-ops { display: none; }
  .cal-live { font-size: 10px; }

  /* ── Filtros en móvil ──────────────────────────────────────────────────────
     El cajón lateral en sí ya NO está aquí: desde 2026-08-13 es el panel de los
     dos tamaños y vive con el resto de `.filter-*` (buscar «CAJÓN LATERAL»).
     Aquí queda solo lo que de verdad depende del ancho. */
  .filter-summary { font-size: var(--fs-xs); }
  /* El resumen numérico de la barra (nº de operaciones, neto…) pasa a línea
     propia: al lado del resumen de filtros no cabe sin partirse por la mitad. */
  .filter-readout { flex-basis: 100%; text-align: left; }
  .dr-inputs { flex-wrap: wrap; }
  .dr-presets { gap: 6px; }
  .dr-preset { flex: 1 0 auto; }
}

/* Puerta de acceso por token (Fase 0.1, auth.js) */
#login-overlay {
  position: fixed; inset: 0; z-index: 2000;
  display: none; align-items: center; justify-content: center;
  background: var(--bg);
}
#login-overlay .login-box {
  width: 320px; max-width: 90vw; padding: 28px 24px;
  background: var(--bg2);
  border: 1px solid var(--bd); border-radius: var(--radius-lg);
  box-shadow: 0 4px 16px var(--shadow); text-align: center;
}
#login-overlay .login-logo { font-size: 22px; font-weight: 700; margin-bottom: 6px; }
#login-overlay .login-logo span { color: var(--a); }
#login-overlay .login-hint { color: var(--dim); font-size: var(--fs-sm); margin-bottom: 16px; }
#login-overlay input {
  width: 100%; height: 38px; padding: 0 12px; margin-bottom: 12px;
  background: var(--input-bg); border: 1px solid var(--bd2); border-radius: var(--radius);
}
#login-overlay input:focus { outline: none; border-color: var(--a); box-shadow: 0 0 0 3px rgba(59,130,246,.12); }
#login-overlay button {
  width: 100%; height: 38px; border: none; border-radius: var(--radius);
  background: var(--a); color: #fff; font-weight: 600;
}
#login-overlay button:hover { background: var(--a2); }
/* El OTRO camino de entrada (2026-08-28). Es un boton porque hace algo, pero se
   pinta como un enlace: dos botones naranjas del mismo tamano no dicen cual es
   la accion principal, y aqui la principal es entrar. */
#login-overlay .login-switch {
  margin-top: 10px; height: auto; padding: 4px;
  background: none; color: var(--dim); font-weight: 400; font-size: var(--fs-sm);
  text-decoration: underline;
}
#login-overlay .login-switch:hover { background: none; color: var(--a); }
#login-overlay .login-error { color: var(--danger); font-size: var(--fs-sm); margin-top: 10px; min-height: 16px; }

/* ── LOS CAJONES DE LA CONSOLA DE TRADINGVIEW (2026-08-28) ───────────────────
   Peticion del usuario: «estos menus se deben abrir como el filtro, modales a la
   derecha, y cuando se pulsa aplicar se cierra y aplica».

   No es un componente nuevo: es EL MISMO modal de siempre, con el overlay
   alineado a la derecha y a toda la altura. Reescribir los seis modales como un
   cajon propio habria sido reescribir seis pantallas para mover una caja -- y
   perder por el camino lo que el overlay ya sabe hacer (avisar de cambios sin
   guardar, cerrarse por el fondo, por Escape y por su boton).

   El ancho es mayor que el del filtro (560 frente a 360): aqui dentro hay
   rejillas de simbolos y tablas de operaciones, no cuatro campos. */
.modal-overlay.tvc-cajon { align-items: stretch; justify-content: flex-end; }
.modal-overlay.tvc-cajon .modal {
  width: min(560px, 96vw); max-width: 96vw;
  height: 100%; max-height: 100%;
  border-radius: 0; border-left: 1px solid var(--bd);
  box-shadow: -2px 0 24px var(--shadow);
}
/* Los anchos que cada modal se pone a mano (min(96vw,1100px) y parecidos) ganarian
   al del cajon: dentro de el, manda el cajon. */
.modal-overlay.tvc-cajon .modal[style] { width: min(560px, 96vw) !important; }
@media (max-width: 760px) {
  /* En movil el cajon es la pantalla entera: 560 px no caben, y un cajon de 96vw
     con 4 px de fondo asomando es un fondo que se pulsa sin querer. */
  .modal-overlay.tvc-cajon .modal,
  .modal-overlay.tvc-cajon .modal[style] { width: 100vw !important; }
}

/* ── EL CUADERNO DE UNA OPERACION (2026-08-28) ───────────────────────────────
   Notas, MAE/MFE y --cuando lo haya-- el diario estructurado, dentro del panel de
   detalle de una operacion. Todo con tokens: es la regla desde el modo oscuro, y
   este bloque nace despues, asi que no tiene excusa. */
ep-trade-journal { display: block; margin-top: 12px; }
/* El DIARIO estructurado. Plegado por defecto cuando esta vacio: la operacion se
   mira mas veces de las que se escribe, y un formulario de quince controles
   siempre abierto empuja las notas fuera de la pantalla. */
.journal-block { border: 1px solid var(--bd); border-radius: var(--radius); margin-bottom: 12px; }
.journal-block > summary { padding: 8px 10px; cursor: pointer; font-weight: 600; font-size: var(--fs-sm); }
.journal-form { padding: 0 10px 10px; display: flex; flex-direction: column; gap: 10px; }
.journal-tri { display: flex; gap: 6px; }
/* El boton elegido se ve ELEGIDO. Sin esto, los tres estados de "segui mi plan"
   se pintan iguales y no hay forma de saber cual esta puesto. */
.journal-tri .btn.active, .journal-tag.on { background: var(--a); border-color: var(--a); color: #fff; }
.journal-tags { display: flex; flex-wrap: wrap; gap: 6px; }
.journal-tag {
  border: 1px solid var(--bd2); border-radius: 999px; background: var(--bg);
  color: var(--tx); font-size: var(--fs-xs); padding: 4px 11px; font-family: inherit;
}
.journal-tag:hover { border-color: var(--a); }
.journal-checklist { display: flex; flex-direction: column; gap: 2px; }
.journal-metric { padding: 10px 12px; gap: 2px; background: var(--bg); box-shadow: none; border: 1px solid var(--bd); }
.journal-metric .metric-value { font-size: 18px; }
.journal-notes { display: flex; flex-direction: column; gap: 8px; }
.journal-note {
  border: 1px solid var(--bd); border-radius: var(--radius);
  background: var(--bg); padding: 8px 10px;
}
.journal-note-head { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.journal-note-head audio { height: 28px; max-width: 220px; }
/* `pre-wrap`: una nota escrita en varias lineas se guarda con sus saltos, y sin
   esto se leeria como un parrafo corrido -- que es justo perder lo que separaba
   "por que entre" de "que hare distinto". */
.journal-note-body { white-space: pre-wrap; font-size: var(--fs-sm); color: var(--tx); }
.journal-note-actions { display: flex; gap: 6px; justify-content: flex-end; margin-top: 6px; }
.journal-edit { width: 100%; }
.journal-composer { display: flex; gap: 8px; align-items: flex-start; margin-top: 10px; }
.journal-composer textarea { flex: 1; min-width: 0; }
.journal-composer-actions { display: flex; flex-direction: column; gap: 6px; align-items: stretch; }
@media (max-width: 760px) {
  /* En movil el boton debajo y a lo ancho: al lado de un textarea se queda en una
     columna de 90 px y el area de toque cae por debajo del minimo. */
  .journal-composer { flex-direction: column; }
  .journal-composer .btn { width: 100%; }
  .journal-composer-actions { width: 100%; }
}

/* ── Paginación de tablas (2026-08-06) ──────────────────────────────────────
   Una sola barra para todas las tablas (ver public/js/paging.js). Cuando todo
   cabe en una página los botones salen DESHABILITADOS en vez de desaparecer: que
   el sitio de los controles cambie según los datos despista más que un botón
   apagado, y así siempre se ve cuántos elementos hay. */
.pager {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--bd);
}
.pager-info { font-size: var(--fs-xs); color: var(--dim); }
.pager-controls { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.pager-page { font-size: var(--fs-xs); color: var(--dim); padding: 0 4px; white-space: nowrap; }
.pager-size { height: 27px; padding: 0 6px; background: var(--input-bg); border: 1px solid var(--bd2); border-radius: var(--radius); color: var(--tx); font-size: var(--fs-xs); }
.pager .btn[disabled] { opacity: .45; cursor: default; }
@media (max-width: 760px) {
  /* En móvil los cinco controles no caben en una línea legible: se apilan y la
     barra ocupa el ancho, que además hace los botones más fáciles de tocar. */
  .pager { flex-direction: column; align-items: stretch; gap: 8px; }
  .pager-controls { justify-content: space-between; }
}

/* ── Infraestructura: matriz de máquinas y flujo de señales (2026-08-12) ─────
   La pantalla contesta dos preguntas distintas y cada una tiene su forma: la
   MATRIZ (una columna por trabajo) es para comparar máquinas de un vistazo, y
   el FLUJO es un camino -- lo que hay que ver ahí es que cada tramo esté
   conectado, así que un tramo roto tiene que leerse como un hueco. */

/* Chips de estado que faltaban: `.chip-warn` se usaba ya en Infraestructura
   ("canaria") y sin regla salía con el gris de `.chip` a secas, o sea que la
   máquina que estrena las versiones nuevas no se distinguía de las demás. */
.chip-ok   { background: var(--pos-soft); color: var(--pos); }
.chip-warn { background: var(--warn-soft); color: var(--warn-tx); }

/* Cabecera de tarjeta con su acción a la derecha. `.card-title` es un bloque,
   así que sin esto el botón caía debajo del título ocupando su propia línea. */
.card-title-flex { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
/* Un desplegable pequeño DENTRO del título de una tarjeta (el rango de días de un
   mapa). No es un filtro de la pantalla --eso es el embudo, §5-- sino un ajuste
   de ESA tarjeta, así que vive con ella y no en la barra de filtros. */
.inline-select {
  background: var(--bg3); border: 1px solid var(--bd); color: var(--tx);
  border-radius: 6px; padding: 3px 8px; font-size: var(--fs-xs); font-weight: 400;
}
.inline-select:focus-visible { outline: 2px solid var(--a2); outline-offset: 1px; }
.card-title-flex .card-title-note { flex: 1 1 auto; }

/* Una fila que lleva a otra pantalla tiene que parecerlo: sin el cursor y el
   fondo al pasar por encima, que se pueda pinchar es un secreto. */
.row-clickable { cursor: pointer; }
.row-clickable:hover > td { background: var(--bg3); }

/* Un enlace dentro de una tabla (el nombre de la máquina que corre un proceso).
   Es un <button> de verdad y no un <a>: no navega a una URL, abre una pantalla
   -- y así conserva el teclado, que un <span> con onclick pierde. */
.link-btn {
  background: none; border: none; padding: 0; font: inherit; color: var(--a2);
  font-weight: 600; cursor: pointer; text-align: left;
}
.link-btn:hover { text-decoration: underline; }

/* Celda de la matriz: el estado arriba y el "cuándo corre" debajo, pequeño. Dos
   líneas y no una: con todo seguido, siete columnas se vuelven ilegibles. */
.infra-matrix td { vertical-align: top; }
.jobcell + .jobcell { margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--bd); }
.jobcell-sub { font-size: var(--fs-xs); color: var(--dim); margin-top: 2px; }

/* Las dos marcas que se repiten en toda la matriz («activo» y «puede») son
   iconos y no palabras (2026-08-13): escritas, lo que se leía al recorrer la
   tabla eran las dos que menos importan. SVG de trazo con `currentColor`, como
   los del menú, así que el color lo pone la clase de estado y no hay dos sitios
   donde cambiarlo. Los estados excepcionales siguen siendo badges de texto. */
/* `vertical-align` porque la marca convive con texto en la leyenda: sin él, la
   línea base del recuadro del SVG la deja montada sobre el renglón. */
.jobmark { display: inline-flex; align-items: center; vertical-align: middle; }
.jobmark svg {
  width: 17px; height: 17px; display: block;
  fill: none; stroke: currentColor; stroke-width: 2;
  stroke-linecap: round; stroke-linejoin: round;
}
/* Verde de positivo: es un hecho, y el mismo verde que `.badge-ok` en el resto
   de la aplicación. */
.jobmark-ok { color: var(--pos); }
/* Gris tenue y trazo discontinuo: una posibilidad no puede pintarse con la misma
   tinta que un hecho -- es un hueco vacío que podría llenarse. */
.jobmark-puede { color: var(--dim2); }
.jobmark-puede svg { stroke-width: 1.8; }
/* La leyenda es texto corrido (no una rejilla): las marcas van dentro de la
   frase que las explica, que es como se aprenden. */
.jobmark-legend { line-height: 1.9; }

/* El flujo: canal -> quién lo escucha -> a qué cuentas se copia. */
.flow { display: flex; flex-direction: column; gap: 14px; }
.flow-row {
  display: flex; align-items: stretch; gap: 10px;
  padding: 12px; background: var(--bg3); border-radius: var(--radius);
}
.flow-col { display: flex; flex-direction: column; gap: 8px; flex: 1 1 0; min-width: 0; }
/* El carril de destinos suele tener varias cuentas: se le da más sitio para que
   no sean tres tarjetas estrechas al lado de una ancha. */
.flow-col-wide { flex: 1.6 1 0; }
/* La flecha la pone el CSS y no el HTML: es la misma pieza girando según el
   ancho, y escrita en el marcado habría que borrarla y volver a ponerla. */
.flow-arrow { display: flex; align-items: center; color: var(--dim); font-size: 20px; flex: none; }
.flow-arrow::before { content: '→'; }
.flow-node {
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius);
  padding: 10px 12px; box-shadow: var(--sh-1);
}
/* Un tramo roto (nadie escucha, nadie copia) se ve en ámbar: es lo que hay que
   encontrar sin leer, porque no lo delata ninguna otra pantalla. */
.flow-node.flow-warn { border-color: var(--warn-bd); background: var(--warn-soft); }
.flow-node.flow-off { opacity: .65; }
.flow-kind { font-size: var(--fs-xs); color: var(--dim); text-transform: uppercase; letter-spacing: .04em; }
.flow-name { font-weight: 600; font-size: var(--fs-md); }
.flow-meta { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; margin-top: 6px; font-size: var(--fs-xs); }

/* "En vivo" en la pantalla de detalle de una máquina: un punto que late. Es la
   diferencia entre una pantalla que se refresca sola y una foto vieja, y sin
   señal ninguna no hay forma de saber cuál de las dos se está mirando. */
.live-dot {
  display: inline-block; width: 7px; height: 7px; border-radius: 50%;
  background: var(--ok); margin-right: 5px; vertical-align: middle;
  animation: live-pulse 2s ease-in-out infinite;
}
@keyframes live-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
/* Quien haya pedido menos movimiento en su sistema no quiere un punto latiendo
   en la esquina: se queda fijo y sigue diciendo lo mismo. */
@media (prefers-reduced-motion: reduce) { .live-dot { animation: none; } }

@media (max-width: 760px) {
  /* El flujo pasa a vertical y las flechas giran: en 380px de ancho, tres
     carriles uno al lado de otro dejan cada tarjeta en una columna de palabras
     partidas. El orden se conserva, que es lo que cuenta el recorrido. */
  .flow-row { flex-direction: column; }
  .flow-arrow { justify-content: center; font-size: 18px; }
  .flow-arrow::before { content: '↓'; }
}

/* ── Arquitectura: la plataforma contada en tres niveles (2026-08-12) ─────────
   Pantalla de PRESENTACIÓN, no de datos: se enseña a alguien de fuera y se
   recorre en directo, así que manda la lectura a distancia (títulos grandes,
   párrafos cortos, un diagrama por apartado) y no la densidad de información.
   Aun así usa el mismo vocabulario que el resto de la aplicación (`.card`,
   `.badge`, `.data-table`, la paleta de `:root`): una pantalla que estrena su
   propio estilo hace parecer que se está enseñando otra aplicación. */

/* Portada. Fondo de acento suave en vez de degradado: la guía de pantallas los
   descarta, y sobre un proyector un degradado se ve a bandas. */
.arq-hero { background: var(--a-soft); border-color: var(--a); }
.arq-kicker {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .08em;
  text-transform: uppercase; color: var(--a2); margin-bottom: 6px;
}
.arq-hero-title { font-size: var(--fs-display); line-height: 1.15; font-weight: 800; letter-spacing: -.01em; }
.arq-hero-lead { font-size: var(--fs-lg); line-height: 1.55; margin-top: 12px; max-width: 78ch; }
.arq-hero-sub  { font-size: var(--fs-md); line-height: 1.6; color: var(--dim); margin-top: 8px; max-width: 78ch; }

/* El dibujo se desplaza dentro de su caja en vez de encoger sin límite: por
   debajo de ~640px las etiquetas del SVG dejan de leerse, y un diagrama que no
   se lee no es un diagrama. */
.arq-figure { margin-top: 18px; overflow-x: auto; }
.arq-figure svg { display: block; width: 100%; min-width: 640px; height: auto; }
.arq-svg-panel { stroke-width: 1; }
.arq-svg-campo   { fill: var(--warn-soft); stroke: var(--warn-bd); }
.arq-svg-nube    { fill: var(--info-soft); stroke: var(--info-bd); }
.arq-svg-cliente { fill: var(--pos-soft); stroke: var(--ok-bd); }
.arq-svg-chip { fill: var(--bg2); stroke: var(--bd); stroke-width: 1; }
.arq-svg-db   { fill: var(--bg2); stroke: var(--bd2); stroke-width: 1; }
.arq-svg-kicker { font-size: 12px; font-weight: 700; letter-spacing: .05em; fill: var(--dim); }
.arq-svg-sm   { font-size: 13px; font-weight: 500; fill: var(--tx); }
.arq-svg-note { font-size: 11.5px; fill: var(--dim); }
.arq-svg-edge { font-size: 11px; fill: var(--dim2); }
.arq-svg-line { stroke: var(--dim2); stroke-width: 1.5; fill: none; }
.arq-svg-dash { stroke-dasharray: 5 4; }
.arq-svg-head { fill: var(--dim2); }

/* Los tres mensajes del ascensor: lo único que hay que recordar de memoria. */
.arq-msg { border-left: 3px solid var(--a); }
.arq-msg-n { font-size: var(--fs-xs); font-weight: 700; color: var(--a2); letter-spacing: .06em; }
.arq-msg-title { font-size: var(--fs-md); font-weight: 700; margin: 6px 0 8px; line-height: 1.35; }
.arq-msg-text { font-size: var(--fs-base); line-height: 1.6; color: var(--dim); }
/* Las cifras se leen a distancia (proyector, pantalla compartida), pero sin
   llegar a `.big`: la lectura principal de esta pantalla es el titular, y dos
   tamaños compitiendo por serlo dejan la portada sin jerarquía. */
.arq-kpis .metric-value { font-size: 28px; }
.arq-cierre { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--bd); color: var(--dim); font-size: var(--fs-base); }

/* Tarjeta de capítulo: el número manda porque en una reunión se dice "vamos al
   cuatro", no el título entero. */
.arq-cap { display: flex; flex-direction: column; gap: 6px; min-height: 150px; }
.arq-cap-n { font-size: var(--fs-xs); font-weight: 700; color: var(--a2); letter-spacing: .08em; }
.arq-cap-title { font-size: var(--fs-md); font-weight: 700; line-height: 1.3; }
.arq-cap-hook { font-size: var(--fs-base); color: var(--dim); line-height: 1.5; flex: 1; }
.arq-cap-foot { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 4px; }
.arq-cap-go { font-size: var(--fs-xs); font-weight: 600; color: var(--a2); }

/* Miga PROPIA de la pantalla: la de la aplicación llega hasta "Arquitectura" y
   no puede saber en qué capítulo se está. El último tramo no es pulsable -- es
   dónde estás, no un sitio al que ir (misma regla que la miga general). */
/* La miga PROPIA de Arquitectura se fue (2026-08-27): la pantalla usa la de la
   aplicación, que desde hoy sabe pintar tramos pulsables que aporta la pantalla.
   Dos migas con dos aspectos en la misma aplicación es lo que había que quitar. */

.arq-chapter-hook { font-size: var(--fs-lg); font-weight: 700; line-height: 1.4; }
.arq-lead { font-size: var(--fs-base); line-height: 1.65; color: var(--dim); margin-top: 8px; max-width: 90ch; }
.arq-lead-big { font-size: var(--fs-md); color: var(--tx); margin-top: 0; }
.arq-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 12px; }
.arq-chip {
  font-size: var(--fs-xs); padding: 3px 10px; border-radius: 999px;
  background: var(--bg3); color: var(--dim); border: 1px solid var(--bd);
}

/* Tarjeta de apartado: el número a la izquierda hace de índice y las etiquetas
   dicen qué trae dentro (diagrama, tabla, detalles). En una reunión eso es lo
   que decide si merece la pena bajar un nivel o seguir. */
.arq-sec { display: flex; gap: 12px; align-items: flex-start; }
.arq-sec-n { font-size: var(--fs-lg); font-weight: 800; color: var(--bd2); line-height: 1.2; flex: none; }
.arq-sec-body { min-width: 0; }
.arq-sec-title { font-weight: 700; font-size: var(--fs-md); line-height: 1.3; }
.arq-sec-lead { font-size: var(--fs-base); color: var(--dim); line-height: 1.55; margin-top: 4px; }
.arq-sec-tags { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.arq-tag {
  font-size: var(--fs-xs); color: var(--dim); background: var(--bg3);
  border-radius: 4px; padding: 1px 7px;
}

.arq-puntos { margin: 16px 0 0 20px; display: flex; flex-direction: column; gap: 9px; }
.arq-puntos li { font-size: var(--fs-base); line-height: 1.62; }
.arq-tabla { margin-top: 16px; }
.arq-notice { display: flex; gap: 10px; align-items: flex-start; margin-top: 16px; line-height: 1.55; }
.arq-detalle { border-top: 1px solid var(--bd); padding: 10px 0; }
.arq-detalle:first-of-type { border-top: none; padding-top: 0; }
.arq-detalle summary { cursor: pointer; font-weight: 600; font-size: var(--fs-base); }
.arq-detalle-body { margin-top: 8px; color: var(--dim); font-size: var(--fs-base); line-height: 1.65; max-width: 90ch; }

/* Pie de navegación: en una reunión se avanza con un botón, no volviendo al
   índice cada vez. `space-between` con un <span> vacío mantiene el botón de
   "siguiente" a la derecha aunque no haya "anterior". */
.arq-pie { display: flex; align-items: center; justify-content: space-between; gap: 10px; flex-wrap: wrap; }

/* ── Diagramas ────────────────────────────────────────────────────────────────
   Dibujados con CSS y no con una librería: son cajas y flechas, y la aplicación
   es vanilla a propósito. Ventaja añadida: heredan la paleta y se reordenan
   solos en móvil, que es justo lo que un SVG a medida no hace. */

/* Planos apilados (campo / plataforma / cliente). */
.arq-layers { display: flex; flex-direction: column; gap: 6px; margin-top: 16px; }
.arq-layer { border: 1px solid var(--bd); border-radius: var(--radius); padding: 12px 14px; }
.arq-layer-campo   { background: var(--warn-soft); border-color: var(--warn-bd); }
.arq-layer-nube    { background: var(--info-soft); border-color: var(--info-bd); }
.arq-layer-cliente { background: var(--pos-soft); border-color: var(--ok-bd); }
.arq-layer-nuevo   { background: var(--bg3); border-style: dashed; }
.arq-layer-title { font-weight: 700; font-size: var(--fs-md); }
.arq-layer-sub { font-size: var(--fs-xs); color: var(--dim); margin-top: 2px; }
.arq-layer-parts { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
.arq-part {
  background: var(--bg2); border: 1px solid var(--bd); border-radius: 6px;
  padding: 3px 9px; font-size: var(--fs-xs);
}
.arq-layers-link { text-align: center; color: var(--dim2); font-size: 11px; letter-spacing: .3em; }

/* Cadena de pasos. En ancho se lee de izquierda a derecha con sus separadores;
   en estrecho los separadores desaparecen y queda una lista numerada, que es
   la misma información sin flechas apuntando al vacío tras un salto de línea. */
.arq-chain { display: flex; flex-wrap: wrap; align-items: stretch; gap: 8px; margin-top: 16px; }
.arq-step {
  flex: 1 1 165px; min-width: 0; background: var(--bg3);
  border: 1px solid var(--bd); border-radius: var(--radius); padding: 10px 12px;
}
.arq-step-n {
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px; border-radius: 50%; background: var(--a); color: #fff;
  font-size: 11px; font-weight: 700; margin-bottom: 6px;
}
.arq-step-title { font-weight: 700; font-size: var(--fs-base); line-height: 1.3; }
.arq-step-text { font-size: var(--fs-xs); color: var(--dim); line-height: 1.5; margin-top: 4px; }
.arq-step-where {
  margin-top: 8px; font-size: 11px; color: var(--dim2);
  text-transform: uppercase; letter-spacing: .04em;
}
.arq-chain-sep { display: flex; align-items: center; color: var(--bd2); font-size: 22px; flex: none; }

/* Uno reparte a varios. */
.arq-fanout { display: flex; align-items: center; gap: 10px; margin-top: 16px; }
.arq-fanout-src {
  flex: 1 1 0; min-width: 0; background: var(--warn-soft); border: 1px solid var(--warn-bd);
  border-radius: var(--radius); padding: 12px 14px;
}
.arq-fanout-sep { color: var(--bd2); font-size: 24px; flex: none; }
.arq-fanout-dst { flex: 1.3 1 0; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.arq-fanout-box { border: 1px solid var(--bd); border-radius: var(--radius); padding: 10px 12px; }

/* Rejilla de conceptos (los diez servicios, las cinco formas de integrar). */
.arq-tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 10px; margin-top: 16px; }
.arq-tile { background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius); padding: 12px; box-shadow: var(--sh-1); }
.arq-tile-title { font-weight: 700; font-size: var(--fs-base); }
.arq-tile-text { font-size: var(--fs-xs); color: var(--dim); line-height: 1.55; margin-top: 5px; }
.arq-tile-chip { margin-top: 9px; font-size: 11px; color: var(--a2); font-weight: 600; }

/* Horizontes. El carril superior es un borde y el punto lo marca: sin la línea,
   cuatro columnas no se leen como una secuencia en el tiempo. */
.arq-timeline { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin-top: 20px; }
.arq-hito { position: relative; border-top: 2px solid var(--bd2); padding-top: 16px; }
.arq-hito-dot {
  position: absolute; top: -6px; left: 0; width: 10px; height: 10px;
  border-radius: 50%; background: var(--bd2);
}
.arq-hito-hecho { border-top-color: var(--ok); }
.arq-hito-hecho .arq-hito-dot { background: var(--ok); }
.arq-hito-encurso { border-top-color: var(--info); }
.arq-hito-encurso .arq-hito-dot { background: var(--info); }
.arq-hito-when { font-size: var(--fs-xs); color: var(--dim); font-weight: 600; letter-spacing: .04em; }
.arq-hito-title { font-weight: 700; font-size: var(--fs-base); margin: 2px 0 8px; line-height: 1.3; }
.arq-hito-items { margin: 10px 0 0 16px; display: flex; flex-direction: column; gap: 6px; }
.arq-hito-items li { font-size: var(--fs-xs); color: var(--dim); line-height: 1.45; }

@media (max-width: 760px) {
  /* La portada baja de 30px a 22px: a 360px, un titular de 30px son cuatro
     líneas y empuja fuera de la primera pantalla todo lo demás. */
  .arq-hero-title { font-size: 22px; }
  .arq-hero-lead { font-size: var(--fs-md); }
  /* La regla de arriba gana por especificidad al `.metric-value` de móvil, así
     que hay que repetirla aquí o las seis cifras salen a 28px en 360px. */
  .arq-kpis .metric-value { font-size: 20px; }
  /* Las flechas se van y la cadena queda como lista: una flecha "›" al final de
     una línea apunta al margen, que es peor que no tener flecha. */
  .arq-chain-sep, .arq-fanout-sep { display: none; }
  .arq-chain { flex-direction: column; }
  .arq-fanout { flex-direction: column; align-items: stretch; }
  .arq-cap { min-height: 0; }
}

/* ── Mapa semanal de caídas de un proceso (2026-08-14) ─────────────────────
   Una semana en franjas de 5 minutos: 288 por día, 7 filas. Es una REJILLA y
   no una tabla de datos a propósito -- lo que se lee aquí es una forma (dónde
   están los huecos rojos, y si se repiten a la misma hora todos los días), no
   una cifra por celda.

   Las celdas son `<i>` sin contenido y con `flex: 1 1 0`: repartirse el ancho
   disponible es lo único que las hace comparables entre días. Con un ancho
   fijo en píxeles, 288 celdas se salen de cualquier modal y el mapa acabaría
   con scroll horizontal, que es justo lo que impide comparar una fila con
   otra de un vistazo. */
.tl-map { display: flex; flex-direction: column; gap: 3px; }
.tl-row { display: flex; align-items: center; gap: 8px; }
.tl-day { flex: 0 0 96px; font-size: var(--fs-xs); color: var(--dim); text-align: right; }
.tl-cells { flex: 1 1 auto; display: flex; gap: 1px; height: 18px; }
.tl-cells i { flex: 1 1 0; min-width: 0; border-radius: 1px; }
/* Verde y rojo son los MISMOS de la paleta de P&L: en esta aplicación el verde
   ya significa "bien" y el rojo "mal" en todas partes, y estrenar aquí otro
   par obligaría a aprender un código de colores nuevo para una sola pantalla. */
.tl-ok { background: var(--pos); }
.tl-ko { background: var(--neg); }
/* Gris CLARO y no un gris medio: "no lo sé" tiene que pesar menos que los dos
   datos, o el ojo lo lee como un tercer estado tan firme como los otros. */
.tl-nd { background: var(--bd); }
.tl-scale { display: flex; justify-content: space-between; margin-left: 104px; font-size: var(--fs-xs); color: var(--dim2); }
.tl-legend { display: flex; gap: 16px; align-items: center; font-size: var(--fs-xs); color: var(--dim); }
.tl-legend i { display: inline-block; width: 12px; height: 12px; border-radius: 2px; vertical-align: -1px; margin-right: 4px; }

@media (max-width: 760px) {
  /* La fecha completa a la izquierda se come el ancho del propio mapa, que es
     lo que hay que ver. Se queda el día en dos cifras. */
  .tl-day { flex: 0 0 44px; overflow: hidden; white-space: nowrap; }
  .tl-scale { margin-left: 52px; }
  .tl-cells { height: 14px; gap: 0; }
}

/* Destello para SEÑALAR una tarjeta a la que se acaba de saltar (2026-08-16).
   Sin él, en una pantalla larga el salto se lee como que el clic no ha hecho
   nada. Usa el color de acento con la opacidad de la paleta, no un amarillo
   inventado. `prefers-reduced-motion` lo deja en un borde fijo: el aviso sigue
   estando, sin el movimiento. */
.card.flash { animation: card-flash 1.2s ease-out; }
@keyframes card-flash {
  0%   { box-shadow: 0 0 0 3px var(--a-soft); }
  100% { box-shadow: 0 0 0 0 transparent; }
}
@media (prefers-reduced-motion: reduce) {
  .card.flash { animation: none; box-shadow: 0 0 0 3px var(--a-soft); }
}

/* ── AYUDA: cajón lateral IZQUIERDO (2026-08-18) ────────────────────────────
   Reaprovecha la caja del cajón de filtros (cabecera / cuerpo con scroll / pie)
   porque es el mismo gesto y ya está probada en los dos tamaños. Lo único que
   cambia es el LADO, y no es estética: filtros y ayuda pueden estar abiertos en
   la misma pantalla, y por el mismo lado serían uno encima de otro. */
.help-panel:not([hidden]) {
  position: fixed; top: var(--nav-h); left: 0; bottom: 0; right: auto;
  width: min(92vw, 420px); z-index: 190; margin: 0;
  display: flex; flex-direction: column;
  background: var(--bg); border: none; border-right: 1px solid var(--bd); border-radius: 0;
  box-shadow: 2px 0 24px rgba(0,0,0,.2);
  padding: 0;
}
.help-panel[hidden] { display: none; }
.help-backdrop:not([hidden]) {
  display: block; position: fixed; top: var(--nav-h); left: 0; right: 0; bottom: 0;
  background: rgba(0,0,0,.4); z-index: 180;
}
.help-backdrop[hidden] { display: none; }
body.help-open { overflow: hidden; }

/* La miga: siempre lleva a la ayuda general. Es lo que impide que la ayuda
   contextual sea un callejón. */
.help-crumb {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 14px; border-bottom: 1px solid var(--bd); background: var(--bg2);
  font-size: var(--fs-sm);
}
.help-crumb-link {
  border: none; background: none; padding: 0; cursor: pointer;
  color: var(--a); text-decoration: underline; font-size: inherit;
}
.help-crumb-link[aria-current="page"] { color: var(--dim); text-decoration: none; cursor: default; }
.help-crumb-sep { color: var(--dim); }
.help-crumb-here { color: var(--tx); font-weight: 600; }

/* El índice: pinchable para saltar a un apartado, con el actual marcado. */
.help-index { display: flex; flex-direction: column; gap: 2px; margin-bottom: 14px; }
.help-index-item {
  text-align: left; border: none; background: none; cursor: pointer;
  padding: 6px 8px; border-radius: var(--radius); color: var(--tx);
  font-size: var(--fs-sm); border-left: 3px solid transparent;
}
.help-index-item:hover { background: var(--bg3); }
.help-index-item.is-current {
  background: var(--bg3); border-left-color: var(--a); font-weight: 600;
}

.help-body h3 { margin: 0 0 8px; font-size: var(--fs-md); }
.help-body p { margin: 0 0 10px; line-height: 1.5; }
.help-body ul, .help-body ol { margin: 0 0 10px; padding-left: 20px; line-height: 1.5; }
.help-body li { margin-bottom: 5px; }
/* Los comandos se copian a mano, así que tienen que poder leerse enteros: se
   parten en vez de salirse, y el bloque no fuerza scroll horizontal al cajón. */
.help-body pre {
  background: var(--bg3); border: 1px solid var(--bd); border-radius: var(--radius);
  padding: 8px 10px; overflow-x: auto; margin: 0 0 10px;
}
.help-body pre code { white-space: pre-wrap; word-break: break-word; font-size: var(--fs-xs); }

.help-otras { border-top: 1px solid var(--bd); padding-top: 12px; margin-top: 6px; }
.help-otra {
  display: inline-block; margin: 0 6px 6px 0; padding: 4px 10px;
  border: 1px solid var(--bd2); border-radius: 999px; background: var(--bg2);
  color: var(--tx); font-size: var(--fs-xs); cursor: pointer;
}
.help-otra:hover { background: var(--bg3); }

/* ── Consola de TradingView: botoneras (2026-08-25, petición del usuario) ─────
   «mejora los botones de acción con iconos... puedes usar barras del tipo
   botonera... distingue las acciones de gráficos y carga de información de las
   de la consola como poner en foco».

   Son DOS barras y se ven distintas a propósito: la de la APLICACIÓN gobierna el
   programa de esa máquina (arrancar, foco, ventana) y la del GRÁFICO actúa sobre
   lo que se está mirando. Mezclarlas es como se acaba minimizando TradingView
   cuando lo que se quería era limpiar unas líneas. */
.tvc-bar {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 8px 10px; border: 1px solid var(--bd); border-radius: 8px;
  background: var(--bg2); margin-bottom: 10px;
}
.tvc-bar + .tvc-bar { margin-top: -4px; }
/* El rótulo de la barra: dice de qué manda cada una sin gastar una línea. */
.tvc-bar-tag {
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; color: var(--dim); margin-right: 4px; white-space: nowrap;
}
.tvc-bar-app { border-left: 3px solid var(--dim); }
.tvc-bar-chart { border-left: 3px solid var(--a); }
/* Separador entre grupos de la misma barra: agrupa sin cajas anidadas. */
.tvc-sep { width: 1px; align-self: stretch; background: var(--bd2); margin: 0 4px; }
/* Botón de la botonera: icono + texto, y en pantallas estrechas solo icono. */
.tvc-btn { display: inline-flex; align-items: center; gap: 6px; }
.tvc-btn svg { width: 15px; height: 15px; flex: 0 0 auto; }
@media (max-width: 700px) {
  .tvc-btn span { display: none; }
  .tvc-btn { padding: 0 9px; }
}
.tvc-bar select, .tvc-bar input[type="text"] { height: 27px; font-size: var(--fs-xs); }

/* ── EL CUERPO Y SU BARRA DE HERRAMIENTAS (2026-08-27) ──────────────────────
   Peticion del usuario: las cinco secciones de configuracion pasan a ser una
   barra de iconos VERTICAL a la derecha, y cada una abre su modal.

   Es un `<aside>` pegado arriba (`position: sticky`): la consola es larga --el
   estado, las capturas, la actividad-- y una caja de herramientas que se va con
   el scroll obliga a subir para cambiar de herramienta. En movil se convierte en
   una fila horizontal encima del cuerpo, porque una columna de iconos a la
   derecha en 360 px de ancho se come un tercio de la pantalla. */
.tvc-layout { display: flex; gap: 14px; align-items: flex-start; }
.tvc-main { flex: 1; min-width: 0; }
.tvc-tools {
  flex: 0 0 auto; display: flex; flex-direction: column; gap: 6px;
  position: sticky; top: 0; padding: 8px; border: 1px solid var(--bd);
  border-radius: 10px; background: var(--bg2);
}
/* LA BARRA LLEVA EL DATO, no solo el icono (2026-08-31, lo pidio el usuario:
   «quitar estas secciones y dejar los datos en la barra de la derecha»). Eran
   tres renglones bajo el grafico, cada uno con su «Aplicar» -- y ese boton de
   fuera hacia parecer que el modal solo configuraba. Ahora cada herramienta dice
   en una linea lo que tiene puesto, y se aplica donde se configura. */
.tvc-tool {
  display: flex; align-items: center; gap: 8px; text-align: left;
  width: 190px; padding: 7px 8px; cursor: pointer; position: relative;
  border: 1px solid transparent; border-radius: 8px;
  background: none; color: var(--dim); font-size: 11px; line-height: 1.25;
  font-family: inherit;
}
.tvc-tool svg { flex: 0 0 auto; }
.tvc-tool-txt { display: flex; flex-direction: column; min-width: 0; }
.tvc-tool-nombre { font-weight: 600; }
/* El dato se recorta con puntos suspensivos: cuatro temporalidades caben, una
   lista de ocho no -- y una barra que crece al aplicar algo es una barra que
   mueve el grafico de sitio. */
.tvc-tool-dato {
  color: var(--dim); opacity: .85; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.tvc-tool.has .tvc-tool-dato { color: var(--a2); opacity: 1; }
.tvc-tool svg { width: 20px; height: 20px; }
.tvc-tool:hover { background: var(--bg3); color: var(--tx); border-color: var(--bd2); }
/* EL PUNTO dice que esa herramienta lleva algo puesto. Sin el, una barra de
   iconos es muda: habria que abrir las cinco para saber cual tiene algo dentro,
   que es peor que la pantalla larga que sustituye. */
.tvc-tool.has { color: var(--a2); }
.tvc-tool.has::after {
  content: ''; position: absolute; top: 6px; right: 8px;
  width: 6px; height: 6px; border-radius: 50%; background: var(--a);
}
@media (max-width: 900px) {
  .tvc-layout { flex-direction: column-reverse; }
  .tvc-tools { flex-direction: row; position: static; width: 100%; overflow-x: auto; }
  /* En estrecho vuelve a ser una tira de iconos: el dato no cabe y lo que
     importa ahi es poder llegar a la herramienta. */
  .tvc-tool { width: auto; min-width: 64px; flex: 1 0 auto; flex-direction: column; text-align: center; }
  .tvc-tool-dato { display: none; }
}

/* LA TIRA DE ESTADO: lo que hay puesto, en un renglon por herramienta.
   (2026-08-29, peticion del usuario: «vamos a quitar las secciones espacio de
   trabajo, dibujar y pine del centro de la consola»). Eran tres tarjetas de
   configuracion ocupando el centro de una pantalla cuyo trabajo es MIRAR un
   grafico. Se quedan --meter algo en un modal solo vale si desde fuera se ve que
   hay dentro-- pero como renglones finos encima del lienzo, no como tarjetas. */
.tvc-estado { display: flex; flex-direction: column; gap: 6px; margin: 12px 0; }
.tvc-estado-item {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 5px 8px; border: 1px solid var(--bd); border-radius: 8px; background: var(--bg2);
}
.tvc-estado-tag {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .4px;
  color: var(--dim); white-space: nowrap;
}
.tvc-estado-txt { flex: 1; min-width: 150px; font-size: var(--fs-xs); color: var(--dim2); }
/* El aviso de «en la maquina hay ahora otra cosa» ocupa su propia linea: en
   medio del renglon empujaria los botones fuera de la vista. */
.tvc-estado-item > .hint { flex-basis: 100%; }

/* EL LIENZO: el centro de la consola es la PANTALLA de la maquina.
   (2026-08-29: «en el centro de la consola tendremos la ultima imagen capturada,
   va a ser como la vista en vivo»). La misma caja ensena la ultima captura y la
   rafaga en vivo -- un solo sitio donde mirar lo que hay en esa pantalla. */
/* LOS DOS EJES DEL ZOOM (2026-08-31). Uno horizontal para el tiempo y otro
   VERTICAL para el precio, que es como se lee un grafico: el eje que se toca esta
   donde esta ese eje. El vertical va rotado porque `appearance: slider-vertical`
   solo lo entienden algunos motores y esto tiene que verse igual en todos. */
.tvc-zoom { display: inline-flex; align-items: center; gap: 8px; }
.tvc-zoom-x { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-xs); }
.tvc-zoom-x input[type="range"] { width: 92px; }
.tvc-zoom-y { display: inline-flex; width: 22px; height: 30px; align-items: center; justify-content: center; }
.tvc-zoom-y input[type="range"] { width: 30px; transform: rotate(-90deg); }
.tvc-zoom input[type="range"] { accent-color: var(--a); cursor: ew-resize; }
.tvc-zoom input[type="range"]:disabled { cursor: not-allowed; opacity: .5; }

/* EL MINI-MAPA DEL REPARTO: elegir sobre que grafica se trabaja (2026-08-30).
   Tiene la FORMA de lo que hay en la pantalla, asi que la pregunta «cual de
   estas» se contesta sin leer. Las celdas van en posicion absoluta con las mismas
   fracciones que dibuja el reparto, para que las dos vistas no puedan divergir. */
.tvc-mapa {
  position: relative; display: inline-block; width: 46px; height: 30px;
  border: 1px solid var(--bd2); border-radius: 4px; background: var(--bg);
  vertical-align: middle; flex: 0 0 auto;
}
.tvc-mapa-celda {
  position: absolute; padding: 0; margin: 0; cursor: pointer;
  border: 1px solid var(--bd2); background: var(--bg2); color: var(--dim);
  font-size: 8px; line-height: 1; font-family: inherit;
}
.tvc-mapa-celda:hover { background: var(--bg3); color: var(--tx); }
/* La elegida se ve DE UN VISTAZO y no por un borde fino: es la que va a recibir
   lo siguiente que se pida, y equivocarse de grafica cuesta trabajo de verdad. */
.tvc-mapa-celda.activa { background: var(--a); border-color: var(--a); color: #fff; font-weight: 700; }

.tvc-canvas {
  border: 1px solid var(--bd); border-radius: 10px; overflow: hidden;
  background: var(--bg2); margin-top: 12px;
}
.tvc-canvas-bar {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 5px 8px; border-bottom: 1px solid var(--bd); background: var(--bg3);
}
.tvc-canvas-img { background: #000; min-height: 160px; display: flex;
  align-items: center; justify-content: center; }
.tvc-canvas-img img { border: 0; border-radius: 0; }
.tvc-canvas-img .empty-state { color: #bbb; padding: 24px 12px; }

/* LA PANTALLA QUE YA NO ES LA PANTALLA (2026-08-30). Mientras no se sepa que
   TradingView responde, la ultima foto se ATENUA y se dice de cuando es: sigue
   sirviendo para reconocer el escritorio, pero no puede hacerse pasar por el
   ahora. Con el velo encima, ademas, las salidas quedan donde se esta mirando. */
.tvc-canvas-img.apagada { position: relative; }
.tvc-canvas-img.apagada img { filter: grayscale(1) brightness(.45); }
.tvc-canvas-velo {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: 16px; color: #eee;
  background: linear-gradient(to bottom, rgba(0,0,0,.35), rgba(0,0,0,.6));
}
.tvc-canvas-velo .hint { color: #ddd; }   /* sobre el velo negro, como la pantalla completa */

/* EL REGISTRO, ABAJO Y BAJO: el panel de VS Code.
   (2026-08-29: «el log estara ocupando la parte inferior con solo 3 lineas
   visibles, el log se mostrara como en visual studio code, las vistas de
   powershell»). Tres lineas de reojo mientras se trabaja, y se despliega cuando
   de verdad hay que leer la historia. */
.tvc-log {
  margin-top: 14px; border: 1px solid var(--bd); border-radius: 10px;
  overflow: hidden; background: var(--bg2);
}
.tvc-log-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 0 6px; border-bottom: 1px solid var(--bd); background: var(--bg3);
}
.tvc-log-tab {
  font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .5px;
  color: var(--tx); padding: 6px 4px; border-bottom: 1px solid var(--a);
}
.tvc-log-toggle {
  border: none; background: none; color: var(--dim); cursor: pointer;
  font-size: 13px; line-height: 1; padding: 4px 8px; border-radius: 6px; font-family: inherit;
}
.tvc-log-toggle:hover { background: var(--bg); color: var(--tx); }
/* TRES LINEAS, y las de arriba detras del scroll. `.tvc-min` mide 22 px, asi que
   el alto se declara en lineas y no en pixeles sueltos: si la fila cambia de
   alto, el panel sigue ensenando tres. */
.tvc-log-body { max-height: calc(22px * 3 + 12px); overflow: auto; padding: 6px 8px; }
.tvc-log.abierto .tvc-log-body { max-height: 40vh; }

/* Los INDICADORES de una grafica: fichas que se quitan una a una, no una cadena
   con comas. Se podian poner varios desde el principio, pero el campo era un
   `datalist` --que deja de proponer en cuanto escribes una coma-- asi que la
   funcion existia y era inalcanzable sin saberselo de memoria. */
.tvc-inds { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 4px; }
.tvc-ind {
  display: inline-flex; align-items: center; gap: 4px;
  background: var(--bg3); border: 1px solid var(--bd2); border-radius: 999px;
  padding: 1px 4px 1px 9px; font-size: var(--fs-xs); color: var(--tx);
}
.tvc-ind button {
  border: none; background: none; cursor: pointer; color: var(--dim);
  font-size: 13px; line-height: 1; padding: 2px 4px; border-radius: 50%;
}
.tvc-ind button:hover { background: var(--bd2); color: var(--danger); }
.tvc-ind-add { display: flex; gap: 4px; align-items: center; flex-wrap: wrap; }
.tvc-ind-add select { max-width: 150px; }

/* Lo que una plantilla deja sin sentido: sigue VIENDOSE --desaparecer sin
   explicacion se lee como que la pantalla esta rota-- pero no se puede tocar. */
.tvc-apagado { opacity: .45; pointer-events: none; }

/* La cuadricula de instrumentos del modal de simbolos. Se VEN, que es lo que un
   `datalist` no hace: alli la lista no existe hasta que escribes algo que case. */
.tvc-sim-grid {
  display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px;
  max-height: 34vh; overflow-y: auto;
}
.tvc-sim-chip {
  border: 1px solid var(--bd2); background: var(--bg); color: var(--tx);
  border-radius: 7px; padding: 6px 12px; cursor: pointer;
  font-size: var(--fs-sm); font-family: inherit; line-height: 1.4;
}
.tvc-sim-chip:hover { background: var(--bg3); }
.tvc-sim-chip.on { background: var(--a); border-color: var(--a); color: #fff; font-weight: 600; }

/* Atajos de periodo de la lista de operaciones. Son un grupo de UNA eleccion
   --como los atajos del rango de fechas de «Cerradas»-- asi que se pintan
   pulsados y llevan `aria-pressed`: sin eso, un lector de pantalla no puede
   decir cual esta puesto. */
.tvc-chips { display: inline-flex; gap: 4px; flex-wrap: wrap; }
.tvc-chip {
  border: 1px solid var(--bd2); background: var(--bg); color: var(--tx);
  border-radius: 999px; padding: 3px 11px; font-size: var(--fs-xs); cursor: pointer;
  line-height: 1.6;
}
.tvc-chip:hover { background: var(--bg3); }
.tvc-chip.on { background: var(--a); border-color: var(--a); color: #fff; }

/* ── La cinta de acciones, minuto a minuto ──────────────────────────────────
   Lo que se lee aquí es una FORMA --qué pasó y en qué orden-- así que es una
   cinta y no una tabla: el tiempo va en el eje y cada acción es una marca. */
.tvc-timeline { display: flex; flex-direction: column; gap: 2px; }
.tvc-min { display: flex; align-items: flex-start; gap: 8px; min-height: 22px; }
.tvc-min-hora {
  flex: 0 0 52px; text-align: right; font-size: var(--fs-xs);
  color: var(--dim); font-variant-numeric: tabular-nums; padding-top: 3px;
}
.tvc-min-marcas { display: flex; gap: 4px; flex-wrap: wrap; padding: 1px 0; }
.tvc-marca {
  display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
  padding: 2px 8px; border-radius: 999px; font-size: var(--fs-xs);
  border: 1px solid transparent; line-height: 1.5;
}
.tvc-marca:hover { filter: brightness(0.96); }
.tvc-marca svg { width: 12px; height: 12px; }
/* Un minuto sin nada: la raya sola. Sin esto, dos acciones separadas por veinte
   minutos se leerían como consecutivas. */
.tvc-hueco { flex: 1; border-top: 1px dashed var(--bd); margin-top: 10px; opacity: .5; }

/* ── El reparto de gráficas se ELIGE VIENDOLO (2026-08-25) ───────────────────
   Lo pidió el usuario: «para mostrar los gráficos ponlo visualmente como
   TradingView». Un desplegable obliga a traducir «2-1» o «4h» a una imagen
   mental antes de poder elegir, y encima con nombres que se parecen demasiado:
   «4 en horizontal» y «4 en vertical» solo se distinguen por la última palabra.
   Agrupado por número de gráficas, porque casi siempre se llega sabiendo
   cuántas se quieren y lo que falta por decidir es la disposición. */
.tvc-vistas { display: flex; flex-direction: column; gap: 2px; }
.tvc-vistas-fila {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 4px 0; border-bottom: 1px solid var(--bd);
}
.tvc-vistas-fila:last-child { border-bottom: 0; }
/* El número no es decoración: sin él, «2×2» y «4 en horizontal» son dos dibujos
   sueltos y no dos formas de hacer lo mismo. */
.tvc-vistas-n {
  width: 18px; flex: 0 0 18px; text-align: center;
  font-size: var(--fs-xs); color: var(--dim);
}
.tvc-vista {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px; padding: 0; cursor: pointer;
  border: 1px solid transparent; border-radius: 6px;
  background: none; color: var(--dim);
}
.tvc-vista:hover { border-color: var(--bd); color: var(--fg); background: var(--bg2); }
/* El elegido se marca con BORDE Y COLOR, no solo con color: en una rejilla de
   doce iconos monocromos, un tono distinto no basta para localizar cuál está
   puesto de un vistazo. */
.tvc-vista.on { border-color: var(--a); color: var(--a); background: var(--bg2); }
.tvc-vista:focus-visible { outline: 2px solid var(--a); outline-offset: 2px; }

/* ── Mirar una imagen a PANTALLA COMPLETA (2026-08-26) ───────────────────────
   Lo pidió el usuario: «permíteme que pueda poner las imágenes de captura y el
   modal de en vivo en pantalla completa para que pueda ver bien los detalles».
   Una captura de un 2×2 de TradingView son ~1900×950 metidos en un modal: a esa
   escala una vela mide un píxel, que es lo contrario de para lo que se saca.

   El estilo en línea reserva 220 px para el título, la nota y los botones del
   modal. En pantalla completa esos 220 px NO existen —no hay modal— y dejarlos
   sería tirar el 20 % de la pantalla justo cuando se ha pedido verlo todo. Por
   eso se recupera aquí, y con el prefijo de WebKit: Safari sigue sin el
   estándar y sin él la imagen se quedaría pequeña en medio navegador. */
#tvi-caja:fullscreen,
#tvc-vivo:fullscreen,
#tvi-caja:-webkit-full-screen,
#tvc-vivo:-webkit-full-screen {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  background: #000; width: 100vw; height: 100vh; padding: 0;
}
#tvi-caja:fullscreen img,
#tvc-vivo:fullscreen img,
#tvi-caja:-webkit-full-screen img,
#tvc-vivo:-webkit-full-screen img {
  max-height: 100vh; max-width: 100vw; margin: 0; border: 0; border-radius: 0;
}
/* Lo que acompaña a la vista en vivo (cuánto queda, «Parar») sigue visible: en
   pantalla completa es donde más falta hace saber que se apaga sola. */
#tvc-vivo:fullscreen .hint,
#tvc-vivo:-webkit-full-screen .hint { color: #ddd; }

/* Barra de progreso hacia el rango o la medalla siguiente (v14, pantalla Progreso).
   Se dibuja con dos cajas y no con <progress>: el elemento nativo se pinta distinto en cada
   navegador y no admite el color de la marca sin trucos por motor. El ancho lo pone quien la
   pinta, en tanto por ciento del hecho medido -- nunca en "operaciones que faltan". */
.prog { height: 6px; border-radius: 4px; background: var(--bg3); overflow: hidden; margin-top: 8px; }
.prog > span { display: block; height: 100%; background: var(--a); border-radius: 4px; }

/* ── La carrera del trader: tarjeta del cuadro de mando y cabecera de Progreso ──
   El rango es lo primero que se lee, así que se pinta como una MARCA (un número grande en un
   disco) y no como una línea de texto más: en un cuadro de mando lleno de cifras de dinero, otra
   cifra en la misma tipografía se pierde. El resto de la tarjeta es contexto de esa marca. */
.rank-card { cursor: pointer; }
.rank-head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin: 4px 0 12px; }
.rank-badge {
  width: 52px; height: 52px; border-radius: 50%; flex: none;
  display: flex; align-items: center; justify-content: center;
  font-size: 24px; font-weight: 700; color: #fff; letter-spacing: -1px;
  background: linear-gradient(135deg, var(--a) 0%, var(--a2) 100%);
  box-shadow: var(--sh-2);
}
.rank-info { flex: 1 1 180px; min-width: 0; }
.rank-name { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.3px; }
.rank-markers { display: flex; gap: 18px; flex-wrap: wrap; }
.rank-markers > div { display: flex; flex-direction: column; }
.rank-markers strong { font-variant-numeric: tabular-nums; }
.prog-lg { height: 10px; }
.rank-next { margin-top: 8px; font-size: var(--fs-sm); color: var(--dim); }
.rank-gate {
  margin-top: 10px; padding: 7px 10px; border-radius: var(--radius);
  background: var(--bg3); border-left: 3px solid var(--warn); font-size: var(--fs-sm);
}
/* Las medallas recientes van como fichas y no como lista: se leen de un vistazo y en móvil se
   reparten solas en varias líneas sin ocupar una fila entera cada una. */
.medal-row { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }
.medal-chip {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 20px; font-size: var(--fs-xs); font-weight: 600;
  background: var(--bg3); border: 1px solid var(--bd);
}

/* ══ Pantalla «Progreso»: el rediseño (2026-08-27, petición del usuario) ══════
   Tres alturas de lectura, y el CSS las separa a propósito: el RESUMEN pesa (disco, degradado,
   cifras grandes), las VÍAS son un mapa que se recorre con la vista (anillos iguales, rejilla) y
   las MEDALLAS son fichas pequeñas donde lo único que cambia es el estado. Si las tres pesaran
   igual, la pantalla sería un muro y no se leería ninguna. */

.section-title {
  font-size: var(--fs-md); font-weight: 700; letter-spacing: -.2px;
  margin: 22px 0 10px;
}
.section-title .dim { font-weight: 400; font-size: var(--fs-sm); }

/* ── El resumen ───────────────────────────────────────────────────────────── */
.hero-progress {
  background: linear-gradient(135deg, var(--a-soft) 0%, var(--bg2) 55%);
  border-left: 3px solid var(--a);
}
.hero-main { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.rank-disc {
  width: 88px; height: 88px; border-radius: 50%; flex: none;
  display: flex; align-items: baseline; justify-content: center; gap: 1px;
  background: linear-gradient(135deg, var(--a) 0%, var(--a2) 100%);
  color: #fff; box-shadow: var(--sh-2);
}
.rank-disc-num { font-size: 40px; font-weight: 800; letter-spacing: -2px; line-height: 1.6; }
.rank-disc-of { font-size: var(--fs-sm); font-weight: 600; opacity: .75; }
.hero-titles { flex: 1 1 260px; min-width: 0; }
.hero-rank { font-size: 24px; font-weight: 800; letter-spacing: -.6px; }
.hero-sub { font-size: var(--fs-sm); color: var(--dim); margin-bottom: 10px; }
.hero-next { margin-top: 8px; font-size: var(--fs-sm); }
.hero-markers {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px; margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--bd);
}
.hero-marker { display: flex; flex-direction: column; gap: 2px; }
.hero-marker-label {
  font-size: var(--fs-xs); color: var(--dim); text-transform: uppercase;
  letter-spacing: .05em; font-weight: 600;
}
.hero-marker-value { font-size: 20px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hero-marker-hint { font-size: var(--fs-xs); color: var(--dim); }
.hero-note { margin-top: 12px; font-size: var(--fs-sm); color: var(--dim); }

/* ── Las puertas ──────────────────────────────────────────────────────────── */
.gates-ok { margin: 14px 0; font-size: var(--fs-sm); color: var(--dim); }
.gates-card { margin-top: 14px; }
.gate-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 8px; }
.gate-detail { flex: 1 1 200px; font-size: var(--fs-sm); }

/* ── Las doce vías ────────────────────────────────────────────────────────── */
.paths-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.path-card {
  display: flex; gap: 12px; align-items: flex-start;
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius-lg);
  padding: 14px; cursor: pointer;
  transition: box-shadow .15s ease, border-color .15s ease, transform .15s ease;
}
.path-card:hover { box-shadow: var(--sh-2); border-color: var(--bd2); transform: translateY(-1px); }
.path-card:focus-visible { outline: 2px solid var(--a); outline-offset: 2px; }
/* Una vía sin empezar se atenúa pero NO se esconde: que exista es la información. */
.path-card.is-off { opacity: .72; }
.path-card.is-active { border-color: var(--a); box-shadow: 0 0 0 1px var(--a) inset; }
.path-body { min-width: 0; flex: 1; }
.path-name { font-weight: 700; font-size: var(--fs-md); letter-spacing: -.2px; }
.path-count { font-size: var(--fs-xs); color: var(--dim); margin-bottom: 6px; }
.path-hint { font-size: var(--fs-xs); color: var(--dim); margin-top: 4px; }

.ring { width: 48px; height: 48px; flex: none; }
.ring-bg { fill: none; stroke: var(--bg3); stroke-width: 5; }
.ring-fg {
  fill: none; stroke: var(--a); stroke-width: 5; stroke-linecap: round;
  transform: rotate(-90deg); transform-origin: 50% 50%;
}
.ring-num { font-size: 17px; font-weight: 700; fill: var(--tx); }

/* ── Las medallas ─────────────────────────────────────────────────────────── */
.medals-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 12px; }
.medal-card {
  background: var(--bg2); border: 1px solid var(--bd); border-radius: var(--radius-lg);
  border-top: 3px solid var(--bd2); padding: 12px 14px; cursor: pointer;
  transition: box-shadow .15s ease, transform .15s ease;
}
.medal-card:hover { box-shadow: var(--sh-2); transform: translateY(-1px); }
.medal-card:focus-visible { outline: 2px solid var(--a); outline-offset: 2px; }
/* El color del filete dice la GRADA de un vistazo; el candado, que no se puede medir. */
/* Los cuatro METALES no se tematizan, y no es un olvido: bronce, plata, oro y
   platino son la identidad del nivel, no colores de interfaz. Un oro que se
   aclarara en oscuro dejaria de ser oro. Van sobre el borde superior de la
   tarjeta, asi que no tienen que sostener texto encima. */
.medal-card.tier-bronze  { border-top-color: #A9743A; }
.medal-card.tier-silver  { border-top-color: #8A94A6; }
.medal-card.tier-gold    { border-top-color: #C79A2E; }
.medal-card.tier-plat    { border-top-color: #4FA3B8; }
.medal-card.tier-diamond { border-top-color: var(--info); }
.medal-card.is-locked { opacity: .66; border-top-style: dashed; }
.medal-head { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 8px; }
.medal-icon { font-size: 22px; line-height: 1; }
.medal-icon-lg { font-size: 24px; margin-right: 6px; }
.medal-name { font-weight: 700; letter-spacing: -.2px; }
.medal-path { font-size: var(--fs-xs); color: var(--dim); }
.medal-state { font-size: var(--fs-sm); font-weight: 600; }
.medal-state.medal-locked { font-weight: 400; color: var(--dim); }
.medal-foot { font-size: var(--fs-xs); color: var(--dim); margin-top: 6px; }

/* ── La escalera de gradas del detalle ────────────────────────────────────── */
.ladder { display: flex; flex-direction: column; gap: 4px; }
.ladder-row {
  display: grid; grid-template-columns: 28px 1fr auto auto; gap: 10px; align-items: center;
  padding: 7px 10px; border-radius: var(--radius); background: var(--bg3);
  font-size: var(--fs-sm);
}
.ladder-row.is-done { background: var(--pos-soft); }
.ladder-row.is-next { box-shadow: 0 0 0 1px var(--a) inset; }
.ladder-icon { font-size: 16px; }
.ladder-name { font-weight: 600; }
.ladder-th { font-variant-numeric: tabular-nums; color: var(--dim); }
.ladder-state { font-size: var(--fs-xs); font-weight: 600; }
.ladder-row.is-done .ladder-state { color: var(--pos); }

@media (max-width: 700px) {
  .rank-disc { width: 66px; height: 66px; }
  .rank-disc-num { font-size: 30px; line-height: 1.7; }
  .hero-rank { font-size: 20px; }
  .paths-grid, .medals-grid { grid-template-columns: 1fr; }
}

/* ── PRIMEROS PASOS: la puesta en marcha (2026-09-02) ─────────────────────────
   Todo color sale de un token, que es la regla de §13 de la guía: la pantalla se
   ve en claro y en oscuro y un hex aquí se habría quedado en claro sobre fondo
   oscuro. El estado de cada paso se dice con el borde izquierdo y no con el fondo
   -- siete tarjetas con fondo de color son un semáforo ilegible. */
.onb-progress { display: flex; flex-direction: column; gap: 8px; }
.onb-progress-head { display: flex; justify-content: space-between; align-items: baseline; }
.onb-bar { height: 8px; border-radius: 999px; background: var(--bg3); overflow: hidden; }
.onb-bar-fill { height: 100%; background: var(--a); transition: width .3s ease; }

.onb-steps { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.onb-step { border-left: 3px solid var(--bd); }
.onb-step.is-done { border-left-color: var(--pos); }
.onb-step.is-todo { border-left-color: var(--a); }
/* Un paso bloqueado se ATENÚA y no se esconde: esconderlo dejaría una lista que
   crece sola y sin explicación, y enseñarlo entero mandaría a una pantalla donde
   todavía no se puede hacer nada. */
.onb-step.is-blocked { opacity: .55; }
.onb-step.is-skip { border-left-color: var(--bd2); }
.onb-step-head { display: flex; align-items: flex-start; gap: 10px; }
.onb-step-n { font-size: 20px; line-height: 1.2; width: 26px; text-align: center; flex: none; }
.onb-step-title { flex: 1; min-width: 0; }
.onb-step-why { margin: 6px 0 0 36px; font-size: var(--fs-sm); }
.onb-step-body { margin: 10px 0 0 36px; display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.onb-step-body-inner { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }

.onb-pkg { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.onb-pkg-row { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
.onb-mono { font-family: ui-monospace, Consolas, monospace; font-size: var(--fs-xs); width: 100%; }
.onb-pending ul { margin: 4px 0 0; padding-left: 18px; }
.onb-pending li { margin-bottom: 3px; }

/* El código de alta se lee EN VOZ ALTA a otra persona que está delante de la
   máquina, así que se separa por letras y se pone grande: es el único dato de la
   aplicación que se transcribe a mano. */
.onb-code { width: 100%; display: flex; flex-direction: column; gap: 8px; }
.onb-code-big {
  font-family: ui-monospace, Consolas, monospace; font-size: 26px; font-weight: 700;
  letter-spacing: 4px; text-align: center; padding: 14px;
  background: var(--bg3); border-radius: var(--radius); color: var(--tx);
}
.onb-foot { margin-top: 12px; }

@media (max-width: 700px) {
  .onb-step-why, .onb-step-body { margin-left: 0; }
  .onb-code-big { font-size: 20px; letter-spacing: 2px; }
}
