/* ==========================================================================
   BASEDASH. Sistema de diseno del backoffice de Mutant Survivors.

   Referencia: styles.refero.design -> Basedash. Spec entera en ~/Downloads/DESIGN.md

   Se carga la ultima, a proposito. Las dos hojas de debajo son casi todas de
   tokens, asi que este primer bloque las re-apunta y 254 declaraciones del
   <style> inline de index.html vienen de regalo.

   Reglas del sistema:
     lienzo NEGRO puro. La tarjeta apenas por encima: se lee hundida, no flotando
     la accion primaria es BLANCA sobre negro. Esa inversion es la firma
     Inter en todo, con tracking NEGATIVO de -0.03em a cualquier tamano
     6px en boton e input, 16px en tarjeta, 999px en chapa. Nunca se cruzan
     violeta y menta SOLO como dato y estado. Jamas como texto ni decoracion
     cero sombras: la elevacion es el contraste, no una proyeccion
     nada dibuja un eje del que no tiene datos

   Desviaciones DECLARADAS, todas por la regla de la casa:
     la tarjeta lleva hairline. El spec la deja a 1,036:1 sobre el lienzo a
       proposito ("carved into the void"), y para una landing vale, pero aqui
       hay 264 tarjetas de datos: sin borde la tarjeta no existe. Se usa el
       Graphite #333333 que el propio spec define como "deep borders".
     existe --edge para el limite de un control, porque 1.4.11 pide 3:1. Sale
       del Steel #808080 del spec, que sobre la tarjeta mide 5,13:1.
     el anillo de foco va a 2px, que 2.4.13 exige.
     se conserva el tema claro, que el spec no contempla, porque ya existe.
     la serie de datos tiene 8 colores y no 2: un backoffice necesita
       distinguir mas de dos series. Los dos del spec entran como s5 y s8.
   ========================================================================== */

:root {
  /* ---- superficies. Negro puro y una tarjeta que apenas se despega. ---- */
  --abyss:  #000000;
  --void:   #000000;   /* lienzo */
  --carbon: #050607;   /* tarjeta: 1,036:1 sobre el lienzo, a proposito */
  --inset:  #0a0b0c;   /* pozo: input, fila de tabla */
  --obsidian: #131415; /* hover y seleccion */
  --rail: #000000;

  /* ---- lineas ---- */
  --graphite: #333333;     /* hairline de tarjeta y divisor. 1,61:1 sobre la tarjeta */
  --graphite-lo: #1c1d1e;  /* separador mas callado */
  --graphite-hi: #808080;  /* Steel: borde fuerte, trazo de icono */
  --edge: #808080;         /* limite de un control. 5,13:1 sobre la tarjeta */

  /* ---- tinta ---- */
  --paper: #ffffff;   /* Ghost White: titulo y texto principal */
  --mist:  #b3b3b3;   /* Ash: cuerpo y contenido de tabla. 9,67:1 sobre la tarjeta */
  --muted: #b3b3b3;
  --faint: #808080;   /* Steel: apoyo y deshabilitado. 5,13:1 */
  --t3: #b3b3b3;
  --t4: #808080;

  /* ---- la accion primaria es blanca sobre negro. Esa es la firma. ---- */
  --ember: #ffffff;
  --on-ember: #000000;
  --toxic: #ffffff;
  --toxic-dim: #e8eaee;
  --toxic-wash: rgba(255, 255, 255, .08);
  --on-toxic: #000000;

  /* ---- los dos acentos cromaticos. Dato y estado. Nunca texto ni fondo. ---- */
  --violet:     #9984d8;   /* Lavender Pulse */
  --mint:       #3fcb7f;   /* Mint Signal */
  --blue:       #9984d8;
  --coral:      #de3e2d;
  --chartreuse: #3fcb7f;
  --pulse: #3fcb7f;
  --amber: #c86100;
  --iris: var(--violet);

  /*
    Serie de datos. El spec define dos colores cromaticos y un backoffice
    necesita distinguir mas de dos series, asi que se conservan los 8 derivados
    (scratchpad/series2.py: la permutacion que maximiza la separacion del peor
    par adyacente bajo protan, deutan y tritan). Medidos ahora sobre la tarjeta
    negra: el peor da 4,61:1, muy por encima del 3:1 que pide un objeto grafico.
    Los dos del spec entran como s5 (lavanda) y s8 (menta) para que el dato y el
    estado hablen el mismo idioma.
  */
  --s1: #3778f5; --s2: #de3e2d; --s3: #009faf; --s4: #c86100;
  --s5: #9984d8; --s6: #dc3860; --s7: #7965f0; --s8: #3fcb7f;

  /* Tinta de TEXTO de cada serie. Un color de serie esta validado como objeto
     grafico (3:1) y como texto pide 4,5:1. Sobre negro casi todos ya cumplen. */
  --s1-ink: #6396f7; --s2-ink: #e8766a; --s3-ink: #00a7b8; --s4-ink: #ef7400;
  --s5-ink: #b3a3e4; --s6-ink: #e6738f; --s7-ink: #9889f3; --s8-ink: #6fdb9d;

  /* ---- geometria: 6 y 16, y no se cruzan ---- */
  --r-ui: 6px;      /* boton, input */
  --r-pill: 999px;  /* chapa */
  --r-card: 16px;
  --r-large: 16px;
  --r-panel: 16px;
  --r-small: 6px;

  --hair: inset 0 0 0 1px var(--graphite);
  --hair-hi: inset 0 0 0 1px var(--graphite-hi);

  /* ==== re-apunte del tema heredado ==== */
  --bg-base: var(--void);
  --bg-surface: var(--carbon);
  --bg-card: var(--carbon);
  --bg-card-hover: var(--obsidian);
  --bg-input: var(--inset);
  --bg-sidebar: var(--abyss);
  --bg-header: var(--void);
  --bg-modal: var(--carbon);
  --border: var(--graphite);
  --border-light: var(--graphite-lo);
  --border-focus: var(--paper);
  --primary: var(--paper);
  --primary-hover: var(--toxic-dim);
  --primary-dark: var(--toxic-dim);
  --primary-glow: var(--toxic-wash);
  --primary-glow-strong: rgba(255, 255, 255, .18);
  --text: var(--mist);
  --text-sec: var(--mist);
  --text-ter: var(--faint);
  --text-muted: var(--faint);
  --white: var(--paper);
  --success: var(--s8-ink);
  --success-dim: color-mix(in srgb, var(--s8) 14%, transparent);
  --warning: var(--s4-ink);
  --warning-dim: color-mix(in srgb, var(--s4) 14%, transparent);
  --error: var(--s2-ink);
  --error-dim: color-mix(in srgb, var(--s2) 14%, transparent);
  --danger-ink: var(--s2-ink);
  --info: var(--s1-ink);
  --info-dim: color-mix(in srgb, var(--s1) 14%, transparent);
  --cyan: var(--s3-ink);
  --cyan-dim: color-mix(in srgb, var(--s3) 14%, transparent);
  --pink: var(--s6-ink);
  --pink-dim: color-mix(in srgb, var(--s6) 14%, transparent);
  --orange: var(--s4-ink);
  --orange-dim: color-mix(in srgb, var(--s4) 14%, transparent);
  --r: var(--r-ui);
  --r-sm: var(--r-small);
  --r-lg: var(--r-card);
  --r-xl: var(--r-card);
  --sidebar-w: 232px;

  /* ---- tipografia. Inter lo hace TODO, y el serif solo el titulo. ---- */
  --font: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-display: 'EB Garamond', 'Cormorant Garamond', Georgia, serif;
  --mono: var(--font);
  --numerals: 'tnum' 1, 'lnum' 1, 'kern' 0;
  /*
    -0.03em en TODO el texto de Inter, sea del tamano que sea. El spec lo pone
    como voz del sistema, no como ajuste optico de un tamano concreto.
  */
  --track: -0.03em;

  --pad-card: 20px;
  --gap-el: 12px;
  --gap-section: 80px;

  /* cero sombras: la elevacion es contraste, no proyeccion.
     OJO: index.html:70-72 declara --sh/--sh-md/--sh-lg con sombras REALES y lo
     unico que las apaga son estas tres lineas. No borrarlas. */
  --sh: none; --sh-md: none; --sh-lg: none;
}

/* ==========================================================================
   tema claro. El spec no lo contempla; la consola ya lo tenia y quitarlo es
   destructivo, asi que se conserva invirtiendo el sistema, no inventandolo.
   ========================================================================== */

:root[data-theme='light'] {
  --abyss:  #f2f2f2;
  --void:   #ffffff;
  --carbon: #fafafa;
  --inset:  #f5f5f5;
  --obsidian: #ececec;
  --rail: var(--abyss);

  --graphite: #d4d4d4;
  --graphite-lo: #e8e8e8;
  --graphite-hi: #6b6b6b;
  --edge: #6b6b6b;          /* 4,90:1 sobre la tarjeta */

  --paper: #000000;
  --mist:  #3d3d3d;
  --muted: #3d3d3d;
  --faint: #5f5f5f;
  --t3: #3d3d3d;
  --t4: #5f5f5f;

  --ember: #000000;         /* la inversion tambien se invierte: negro sobre blanco */
  --on-ember: #ffffff;
  --toxic: #000000;
  --toxic-ink: #000000;
  --toxic-dim: #1c1c1c;
  --toxic-wash: rgba(0, 0, 0, .06);
  --on-toxic: #ffffff;

  --violet: #5b46a8;
  --mint: #10693c;
  --blue: #5b46a8;
  --coral: #c32f1f;
  --pulse: #10693c;
  --amber: #a55000;

  --s5: #5b46a8; --s8: #10693c;
  --s1-ink: #0c5af1; --s2-ink: #c32f1f; --s3-ink: #00727e; --s4-ink: #a55000;
  --s5-ink: #5b46a8; --s6-ink: #c6234b; --s7-ink: #624aed; --s8-ink: #10693c;

  --border-focus: var(--paper);
  --primary: var(--paper);

  --hair: inset 0 0 0 1px var(--graphite);
  --hair-hi: inset 0 0 0 1px var(--graphite-hi);

  color-scheme: light;
}

:root { --toxic-ink: var(--toxic); }

:root[data-theme='light'] .sidebar-logo .game,
:root[data-theme='light'] .login-logo .game,
:root[data-theme='light'] .nav-item.active svg,
:root[data-theme='light'] .save-tree-row .st-key,
:root[data-theme='light'] .badge-primary,
:root[data-theme='light'] .nx-kpi-value.hero { color: var(--toxic-ink); }

/*
  The mark ships white on transparent. On a white page that is a blank
  rectangle, so light mode inverts it to the ink colour. Cheaper and sharper
  than shipping a second PNG, and it tracks the token if the ink ever moves.
*/
:root[data-theme='light'] .logo-mark { filter: invert(1) brightness(.12); }

:root[data-theme='light'] .nx-glasstip {
  background: rgba(255, 255, 255, .90);
  border-color: rgba(16, 19, 22, .10);
  box-shadow: 0 10px 34px rgba(16, 19, 22, .16), inset 0 1px 0 rgba(255, 255, 255, .7);
}
:root[data-theme='light'] .modal-backdrop,
:root[data-theme='light'] .modal-overlay { background: rgba(16, 19, 22, .38); }

:root[data-theme='light'] .nx-grid-line { stroke: #EDEFF2; }
:root[data-theme='light'] .login-wrapper::before { background: radial-gradient(circle, rgba(58,120,13,.06) 0%, transparent 70%); }
:root[data-theme='light'] .login-wrapper::after { background: radial-gradient(circle, rgba(58,120,13,.04) 0%, transparent 70%); }

/* the theme switch itself */
.nx-theme-toggle {
  display: flex; align-items: center; gap: .375rem; width: 100%;
  padding: .4rem .625rem; margin-bottom: .375rem;
  background: transparent; border: 0; cursor: pointer;
  border-radius: var(--r-ui); color: var(--muted); font-size: .75rem;
  font-family: var(--font);
}
.nx-theme-toggle:hover { background: var(--obsidian); color: var(--paper); }
.nx-theme-toggle svg { width: 14px; height: 14px; flex-shrink: 0; }
.nx-theme-toggle .nx-theme-name { flex: 1; text-align: left; }

/* ==========================================================================
   base
   ========================================================================== */

/*
  La escala de la consola.

  Estaba clavada en 16px, y con el cuerpo a .8125rem eso son 13px de texto
  corrido y 11px en las notas: correcto sobre el papel y pequeno en una pantalla
  de trabajo que se mira ocho horas. Subir la raiz sube todo a la vez y mantiene
  las proporciones, que es lo que no pasaria tocando declaraciones sueltas.

  17px deja el cuerpo en ~13,8px y las notas en ~11,7px. Se toca aqui, no abajo.
*/
html { font-size: 17px; }

html, body {
  background: var(--void);
  color: var(--mist);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'cv11', 'ss01';
}

body { font-family: var(--font); letter-spacing: var(--track); }

h1, h2, h3, h4 {
  color: var(--paper);
  letter-spacing: var(--track);
  font-weight: 500;
}

/*
  Every digit in this product is tabular. A column of proportional numerals
  makes 1.481 and 8.888 different widths, and the eye reads the ragged edge
  as a difference in magnitude that is not there.
*/
.mono, .stat-value, .akpi-value, table td, table th,
.nx-num, [class*='-value'], input[type='number'] {
  font-variant-numeric: tabular-nums;
}

.mono, .nx-num { font-family: var(--mono); font-variant-numeric: tabular-nums; }

/*
  One focus ring for the whole console. Toxic on void measures 12.1:1, so it
  survives any surface under it, and the dark gasket keeps it legible when it
  lands on the toxic button itself, where green on green would vanish.
*/
*:focus-visible {
  outline: 2px solid var(--toxic);
  outline-offset: 2px;
  box-shadow: 0 0 0 4px var(--void);
  border-radius: 3px;
}

::selection { background: var(--toxic); color: var(--on-toxic); }

::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-thumb { background: var(--graphite); border-radius: 9999px; }
::-webkit-scrollbar-thumb:hover { background: var(--graphite-hi); }
::-webkit-scrollbar-track { background: transparent; }

/*
  color-scheme belongs to the document, not to the field. Pinned to dark here it
  painted the native controls black in the light theme: date pickers, select
  arrows, spinners, 26 black squares down the player list. :root already
  declares it per theme.
*/
input, select, textarea { color-scheme: inherit; }

/* the legacy sheet animates every section in; it reads as lag on a dense screen */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
  }
}

/* ==========================================================================
   shell: sidebar, nav, page header
   ========================================================================== */

/*
  La barra lateral tiene su propio suelo.

  Compartia el --void de la pagina y lo unico que la separaba era un hairline de
  medio pixel, que a 1,60:1 sobre negro no es una separacion, es una sugerencia.
  Un plano mas oscuro que el contenido la ancla y de paso hace que las tarjetas
  parezcan estar por encima, que es justo lo que son.
*/
.sidebar {
  background: var(--rail);
  border-right: 0;
  box-shadow: inset -1px 0 0 var(--graphite);
}

.sidebar-header { padding: 1.125rem 1rem .625rem; }

/*
  The official NexGame mark, exported from branding/nexgame_clean.png. It ships
  as white on transparent, which is exactly what this ground wants: the logo
  carries the brand and the accent stays free to mean "this is the thing you
  selected". Drawn at a third of its pixel width so it stays sharp on retina.
*/
.sidebar-logo { align-items: baseline; gap: .45rem; }
.logo-mark { width: 104px; height: auto; display: block; }
.logo-mark-lg { width: 168px; margin: 0 auto .75rem; }

/* the old text wordmark, kept in case a view still renders it */
.sidebar-logo .logo-text { font-weight: 600; letter-spacing: var(--track); font-size: 1.0625rem; }
.sidebar-logo .nex { color: var(--paper); }
.sidebar-logo .game { color: var(--toxic); }
.sidebar-logo .version {
  font-family: var(--mono); font-size: .5625rem; color: var(--muted);
  background: transparent; box-shadow: var(--hair-hi); border-radius: 4px;
  padding: .1rem .3rem; letter-spacing: var(--track);
}

.sidebar-admin {
  background: transparent; border: 0; box-shadow: var(--hair);
  padding: .5rem .625rem; margin-top: .875rem; border-radius: var(--r-ui);
}

.sidebar-nav { padding: .375rem .5rem; }

.nav-section {
  font-size: .5625rem; letter-spacing: var(--track); text-transform: uppercase;
  color: var(--faint); padding: 1rem .75rem .3rem; font-weight: 500;
}

.nav-item {
  padding: .4rem .75rem; font-size: .8125rem; font-weight: 500;
  color: var(--muted); border-radius: var(--r-ui); margin-bottom: 0;
  transition: color .12s ease, background .12s ease;
}
.nav-item:hover { background: var(--obsidian); color: var(--paper); }
.nav-item.active {
  background: color-mix(in srgb, var(--toxic) 9%, var(--obsidian));
  color: var(--paper); font-weight: 500;
}
.nav-item.active::before {
  /* De borde a borde: la marca dice donde estas, no adorna la fila. */
  width: 2px; height: 100%; top: 0; background: var(--toxic); border-radius: 0;
}
.nav-item.active svg { color: var(--toxic); }
.nav-item svg { width: 16px; height: 16px; opacity: .55; }
.nav-item.active svg { opacity: 1; color: var(--toxic); }
.nav-item .nav-badge {
  background: transparent; color: var(--mist); font-weight: 500;
  box-shadow: inset 0 0 0 1px var(--edge);
  font-size: .5625rem; padding: .05rem .35rem;
}

.sidebar-footer { border-top: 0; box-shadow: inset 0 1px 0 var(--graphite); }

.page-header { margin-bottom: .875rem; animation: none; }
.page-header h1 {
  display: flex; align-items: center; gap: .5rem;
  font-size: 1.0625rem; font-weight: 500; letter-spacing: var(--track); color: var(--paper);
}
.page-header h1 svg { color: var(--edge); width: 15px; height: 15px; flex: 0 0 auto; }
.page-header p { font-size: .8125rem; color: var(--muted); margin-top: .2rem; }

/*
  Con la barra superior, el titulo de pagina sobra.

  Decia lo mismo que el breadcrumb, dos veces y a dos centimetros. Antes hacia
  falta porque no habia marco; ahora el marco lo dice y lo que gana la pantalla
  es que la primera linea de contenido sea el veredicto, no su propio nombre.
  Se conserva el h1 en el DOM: es el encabezado de la region y un lector de
  pantalla lo necesita aunque no ocupe pixeles.
*/
.page-header.is-label {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ==========================================================================
   surfaces
   ========================================================================== */

.card, .achart-card, .gd-panel, .cb-group {
  background: var(--carbon);
  border: 0;
  box-shadow: var(--hair);
  border-radius: var(--r-card);
  padding: 1rem;
}

.card-header { margin-bottom: .75rem; }
.card-header h3, .card-h { font-size: .8125rem; font-weight: 500; color: var(--paper); letter-spacing: var(--track); }

/*
  The old stat card carried a 40px tinted icon square, a coloured blob bleeding
  out of the corner, a lift on hover and a drop shadow. Four decorations for
  one number. What is left is the number, its label, and the hairline.
*/
.stat-card {
  background: var(--carbon); border: 0; box-shadow: var(--hair);
  border-radius: var(--r-card); padding: .875rem 1rem;
}
.stat-card::after { display: none; }
.stat-card:hover { border-color: transparent; transform: none; box-shadow: var(--hair); }
/*
  The icon on a stat card was a 40px tinted square whose colour was picked per
  card: green for players, cyan for revenue, pink for purchases. Six hues that
  encode nothing, sitting above six numbers that do. The glyph stays as a way
  to find a card by shape, in one ink.
*/
.stat-card .stat-icon {
  width: 22px; height: 22px; border-radius: 0; margin-bottom: .5rem;
  background: transparent !important; color: var(--muted) !important;
}
.stat-card .stat-icon svg { width: 15px; height: 15px; stroke: var(--muted) !important; color: var(--muted) !important; }
.stat-card .stat-icon svg [fill]:not([fill='none']) { fill: var(--muted) !important; }

/* the value is the value; it only takes ink when the number is itself a verdict */
.stat-card .stat-value { color: var(--paper); }
.stat-card .stat-value.success,
.stat-card .stat-value.cyan,
.stat-card .stat-value.primary,
.stat-card .stat-value.info,
.stat-card .stat-value.pink { color: var(--paper); }
.stat-card .stat-value.error { color: var(--coral); }
.stat-card .stat-value.warning { color: var(--amber); }
.stat-card .stat-value {
  font-family: var(--mono); font-size: 1.375rem; font-weight: 500;
  letter-spacing: var(--track); color: var(--paper);
}
.stat-card .stat-label {
  font-family: var(--mono); font-size: .5938rem; font-weight: 400;
  color: var(--muted); letter-spacing: var(--track); margin-top: .3rem;
}
.stat-card .stat-change { padding: 0; background: transparent !important; font-weight: 500; }

/* ==========================================================================
   KPI strip. Tiles are separated by one hairline, not by a gutter of air.
   ========================================================================== */

.akpi-grid, .nx-kpirow {
  display: grid; gap: 1px; background: var(--graphite);
  box-shadow: var(--hair); border-radius: var(--r-card); overflow: hidden;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}
.akpi, .nx-kpi {
  background: var(--carbon); border: 0; border-radius: 0;
  padding: .75rem .875rem; display: flex; flex-direction: column; gap: .3rem;
  min-width: 0;
}
.akpi-label, .nx-kpi-label {
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--muted); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/*
  La cifra manda.

  Estaba en 1,3125rem, casi el mismo cuerpo que un titulo de tarjeta, asi que en
  una fila de seis KPI nada destacaba sobre nada: la pantalla pesaba igual en
  todos sus puntos. Subirla y apretarle el tracking es lo que separa un panel de
  datos de una lista de parejas etiqueta-valor, y no cuesta un pixel de alto
  porque la interlinea ya estaba a 1.
*/
.akpi-value, .nx-kpi-value {
  font-family: var(--mono); font-size: 1.75rem; line-height: 1.05;
  font-weight: 500; letter-spacing: var(--track); color: var(--paper);
  font-variant-numeric: tabular-nums;
}
.akpi-sub, .nx-kpi-hint { font-size: .6875rem; color: var(--muted); margin-top: .25rem; }

/*
  Un filo claro de un pixel arriba.

  Es como se lee una superficie iluminada desde arriba, y es lo unico que da
  volumen sin recurrir a una sombra. Va con la elevacion, no contra ella: el
  filo solo existe donde hay un escalon.
*/
.card, .achart-card, .stat-card, .akpi, .nx-kpi {
  position: relative;
}
.card::before, .achart-card::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 1px;
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--paper) 7%, transparent) 18%, color-mix(in srgb, var(--paper) 7%, transparent) 82%, transparent);
  border-radius: var(--r-card) var(--r-card) 0 0;
  pointer-events: none;
}
:root[data-theme='light'] .card::before,
:root[data-theme='light'] .achart-card::before { display: none; }

/*
  The legacy sheet tinted a KPI by which class the author happened to type, not
  by what the number meant: "Win Rate 4,2%" was green because the card had
  class success, and a 4,2% win rate is the worst number on the screen. Ink is
  a verdict, so the legacy classes lose it and only the deliberate ones keep it.
*/
.akpi-value.success, .akpi-value.warning, .akpi-value.error,
.akpi-value.primary, .akpi-value.cyan, .akpi-value.pink, .akpi-value.info { color: var(--paper); }
/*
  Los tonos deliberados, en las dos anatomias.

  .akpi y .nx-kpi son el mismo pixel desde arriba, pero good/bad/warn solo
  estaban declarados para .nx-kpi. Habia dos sitios calculando su tono con un
  umbral real (dias sin cobrar > 30, devoluciones > 5% del bruto) y pintandolo
  en tinta normal porque la clase no existia: "152 dias sin cobrar" salia como
  un dato cualquiera. Se calculaba la alarma y se tiraba.
*/
.nx-kpi-value.good, .akpi-value.good { color: var(--pulse); }
.nx-kpi-value.bad,  .akpi-value.bad  { color: var(--coral); }
.nx-kpi-value.warn, .akpi-value.warn { color: var(--amber); }
.nx-kpi-value.hero { color: var(--toxic); }

.nx-kpi-row { display: flex; align-items: baseline; gap: .4rem; }
.nx-delta { font-family: var(--mono); font-size: .6875rem; flex-shrink: 0; }
.nx-delta.up { color: var(--pulse); }
.nx-delta.down { color: var(--coral); }
.nx-delta.flat { color: var(--muted); }
.nx-spark { display: block; width: 100%; height: 24px; margin-top: .1rem; }

/* ==========================================================================
   chart cards
   ========================================================================== */

.achart-card { padding: .875rem 1rem 1rem; }
.achart-header { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.achart-header h3 { font-size: .8125rem; font-weight: 500; color: var(--paper); }
.achart-meta { font-family: var(--mono); font-size: .625rem; color: var(--muted); letter-spacing: var(--track); }

/*
  The label that carries a band's sample size. It is a heading, not a footnote:
  a mean over two saves and a count over forty two players cannot sit in one
  grid without saying which is which.
*/
.nx-band-label {
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--faint); font-weight: 400;
  margin: 1.5rem 0 .5rem; display: flex; align-items: center; gap: .625rem;
}
.nx-band-label::after { content: ''; flex: 1; height: .5px; background: var(--graphite); }

.a-section-title {
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--faint); font-weight: 400;
  margin: 1.75rem 0 .625rem; display: flex; align-items: center; gap: .625rem;
}
.a-section-title::after { content: ''; flex: 1; height: .5px; background: var(--graphite); }

/* the sentence printed under a chart that could be read as saying more than it does */
.nx-note {
  margin-top: .625rem; font-size: .6875rem; line-height: 1.5; color: var(--muted);
}

/*
  Nothing renders an axis for a series it does not have. An empty chart says
  what is missing and why, because a flat line at zero is a claim about the
  game and "we are not collecting this yet" is a claim about the pipeline.
*/
.nx-empty {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: .375rem; min-height: 120px; padding: 1.5rem 1rem; text-align: center;
}
.nx-empty-title { font-size: .8125rem; color: var(--mist); }
.nx-empty-why { font-size: .6875rem; color: var(--muted); max-width: 42ch; line-height: 1.5; }

/* ==========================================================================
   "Qué mirar hoy". The interpretation layer, above every chart.
   ========================================================================== */

.nx-alerts { background: var(--carbon); box-shadow: var(--hair); border-radius: var(--r-card); overflow: hidden; }
.nx-alerts-head {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  padding: .625rem 1rem; box-shadow: inset 0 -1px 0 var(--graphite);
}
.nx-alerts-head h2 { font-size: .8125rem; font-weight: 500; }
.nx-alerts-counts { display: flex; gap: .75rem; font-family: var(--mono); font-size: .6875rem; }
.nx-alerts-counts .c-critical { color: var(--coral); }
.nx-alerts-counts .c-warning { color: var(--amber); }
.nx-alerts-counts .c-info, .nx-alerts-counts .c-rules { color: var(--muted); }

.nx-alert {
  display: flex; align-items: flex-start; gap: .75rem; padding: .625rem 1rem;
  box-shadow: inset 0 -1px 0 rgba(35, 37, 42, .6); cursor: pointer;
  transition: background .12s ease; width: 100%; text-align: left; background: none; border: 0;
}
.nx-alert:hover { background: var(--obsidian); }
.nx-alert:last-of-type { box-shadow: none; }
.nx-alert-dot { width: 6px; height: 6px; border-radius: 9999px; margin-top: .45rem; flex-shrink: 0; }
.nx-alert-dot.critical { background: var(--coral); }
.nx-alert-dot.warning { background: var(--amber); }
.nx-alert-dot.info { background: var(--s1); }
.nx-alert-body { flex: 1; min-width: 0; }
.nx-alert-title { display: block; font-size: .8125rem; color: var(--paper); }
.nx-alert-msg { display: block; font-size: .75rem; line-height: 1.5; color: var(--muted); margin-top: .15rem; }
.nx-alert-where { font-family: var(--mono); font-size: .625rem; color: var(--faint); margin-top: .2rem; }
.nx-alert-metric { text-align: right; flex-shrink: 0; }
.nx-alert-metric .v { font-family: var(--mono); font-size: .8125rem; }
.nx-alert-metric .v.critical { color: var(--coral); }
.nx-alert-metric .v.warning { color: var(--amber); }
.nx-alert-metric .v.info { color: var(--mist); }
.nx-alert-metric .l { display: block; font-size: .5938rem; color: var(--muted); margin-top: .1rem; }

/* silence is a result: say which rules ran and found nothing */
.nx-alerts-quiet { padding: .75rem 1rem; font-size: .75rem; color: var(--muted); line-height: 1.5; }
.nx-alerts-more {
  width: 100%; padding: .5rem; font-size: .75rem; color: var(--muted);
  background: none; border: 0; cursor: pointer; box-shadow: inset 0 1px 0 var(--graphite);
}
.nx-alerts-more:hover { color: var(--paper); }

/* ==========================================================================
   analytics views. Four questions, one at a time, instead of one column of
   forty charts in the order they were written.
   ========================================================================== */

.nx-viewbar {
  display: flex; gap: 2px; margin: 1rem 0 .25rem;
  box-shadow: inset 0 -1px 0 var(--graphite);
}
.nx-viewtab {
  padding: .5rem .875rem; font-size: .8125rem; font-weight: 500; color: var(--muted);
  background: none; border: 0; cursor: pointer; border-bottom: 1.5px solid transparent;
  transition: color .12s ease;
}
.nx-viewtab:hover { color: var(--mist); }
.nx-viewtab.active { color: var(--paper); border-bottom-color: var(--toxic); font-weight: 500; }

.nx-viewq { font-size: .75rem; color: var(--muted); margin: .625rem 0 0; }

/* the first section title inside a view does not need the top margin of a divider */
.nx-view > .a-section-title:first-child { margin-top: 1.25rem; }

/* ==========================================================================
   controls
   ========================================================================== */

.filter-btn {
  height: 32px; padding: 0 .75rem; font-size: .75rem; font-weight: 500;
  border-radius: var(--r-ui); background: var(--toxic); color: var(--on-toxic);
  letter-spacing: var(--track); border: 0;
}
.filter-btn:hover { background: #7DF52E; }
.filter-btn.secondary {
  background: transparent; color: var(--mist); border: 0; box-shadow: inset 0 0 0 1px var(--edge);
}
.filter-btn.secondary:hover { background: var(--obsidian); color: var(--paper); border-color: transparent; }

input[type='text'], input[type='search'], input[type='number'], input[type='date'],
input[type='email'], input[type='password'], select, textarea, .search-input, .form-input {
  background: var(--void) !important; color: var(--paper);
  border: 0 !important; box-shadow: inset 0 0 0 1px var(--edge);
  border-radius: var(--r-ui); font-size: .8125rem;
}
input::placeholder, textarea::placeholder { color: #727983; }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; box-shadow: inset 0 0 0 1.5px var(--toxic); border-radius: var(--r-ui);
}

/* segmented pills: window pickers, segment filters, chart granularity */
.tabs, .atabs { box-shadow: inset 0 -1px 0 var(--graphite); gap: 0; }
.tab, .atab {
  padding: .5rem .875rem; font-size: .8125rem; font-weight: 500; color: var(--muted);
  border-bottom: 1.5px solid transparent; background: none;
}
.tab:hover, .atab:hover { color: var(--mist); }
.tab.active, .atab.active {
  color: var(--paper); border-bottom-color: var(--toxic); font-weight: 500;
  background: none !important;
}

.ppill, .seg-pill, .filter-pill {
  height: 26px; padding: 0 .625rem; font-size: .6875rem; font-weight: 500;
  border-radius: var(--r-ui); color: var(--muted); background: transparent;
  box-shadow: inset 0 0 0 1px var(--graphite-hi); border: 0;
}
.ppill:hover, .seg-pill:hover, .filter-pill:hover { color: var(--paper); background: var(--obsidian); }
.ppill.active, .seg-pill.active, .filter-pill.active {
  background: var(--obsidian); color: var(--paper); box-shadow: inset 0 0 0 1px var(--edge);
}

/* ==========================================================================
   badges
   ========================================================================== */

.badge {
  padding: .1rem .4rem; border-radius: 4px; font-size: .625rem; font-weight: 500;
  font-family: var(--mono); letter-spacing: var(--track);
}
.badge-gray { background: transparent; color: var(--muted); box-shadow: inset 0 0 0 1px var(--graphite-hi); }
.badge-primary { background: var(--toxic-wash); color: var(--toxic); }
.badge-pink { background: rgba(213, 81, 129, .14); color: var(--s5); }
.badge-cyan, .badge-info { background: rgba(57, 135, 229, .14); color: var(--s1); }
.badge-orange { background: rgba(217, 89, 38, .14); color: var(--s2); }

/* ==========================================================================
   tables. Density is the point: a 50px row on 42 players wastes a screen.
   ========================================================================== */

.table-wrap { border-radius: var(--r-card); overflow: auto; }

table { border-collapse: collapse; width: 100%; font-size: .8125rem; }

table thead th {
  font-family: var(--mono); font-size: .5938rem; font-weight: 400;
  text-transform: uppercase; letter-spacing: var(--track); color: var(--muted);
  padding: .5rem .625rem; text-align: left; white-space: nowrap;
  background: transparent; box-shadow: inset 0 -1px 0 var(--graphite);
}

table tbody td {
  padding: .4375rem .625rem; color: var(--mist);
  box-shadow: inset 0 -1px 0 rgba(35, 37, 42, .55);
  border: 0; vertical-align: middle;
}
table tbody tr:last-child td { box-shadow: none; }
table tbody tr:hover td { background: var(--obsidian); }
table tbody tr td:first-child { color: var(--paper); }

/* anything that is a quantity sits right and reads mono */
table td.num, table th.num, table td[data-num], .data-table td.num { text-align: right; font-family: var(--mono); }

/* ==========================================================================
   chart primitives rendered by nexus-charts.js
   ========================================================================== */

.nx-chart { position: relative; width: 100%; }
.nx-chart svg { display: block; width: 100%; max-width: 100%; }
.nx-grid-line { stroke: #1E2024; stroke-width: 1; }
.nx-axis-text { fill: var(--faint); font-size: 10px; font-family: var(--mono); }
.nx-crosshair { stroke: var(--graphite-hi); stroke-width: 1; }

.nx-legend { display: flex; flex-wrap: wrap; gap: .3rem .875rem; font-size: .6875rem; color: var(--muted); }
.nx-legend-item { display: flex; align-items: center; gap: .375rem; }
.nx-legend-dot { width: 7px; height: 7px; border-radius: 9999px; flex-shrink: 0; }

/* ==========================================================================
   the glass readout

   Every chart in the console raises the same floating card, following the
   cursor. This is the one surface that takes a blur and a real shadow instead
   of a hairline: a hairline says "this panel sits on the page", and a tooltip
   does not sit on the page, it floats over content it cannot predict. The blur
   separates it from whatever is underneath, the shadow says how far above.
   ========================================================================== */

.nx-glasstip {
  position: fixed; z-index: 9999; display: none; pointer-events: none;
  min-width: 132px; max-width: 300px; padding: .625rem .75rem;
  border-radius: 10px;
  background: rgba(15, 16, 17, .88);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow: 0 10px 34px rgba(0, 0, 0, .55), inset 0 1px 0 rgba(255, 255, 255, .05);
  color: var(--mist);
  animation: nxTipIn .11s ease-out;
}
@keyframes nxTipIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: none; } }

.nx-glasstip-t {
  font-family: var(--mono); font-size: .6875rem; color: var(--paper);
  letter-spacing: var(--track); padding-bottom: .375rem; margin-bottom: .375rem;
  box-shadow: inset 0 -1px 0 rgba(255, 255, 255, .09);
}
.nx-glasstip-rows { display: flex; flex-direction: column; gap: .25rem; }
.nx-glasstip-row { display: flex; align-items: center; gap: .5rem; font-size: .75rem; }
.nx-glasstip-l { flex: 1; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.nx-glasstip-v { font-family: var(--mono); color: var(--paper); font-variant-numeric: tabular-nums; }

/* anything wired to the glass card reacts to the cursor */
.nx-hit { cursor: default; transition: opacity .1s ease; }
svg .nx-hit:hover { opacity: .82; }
div.nx-hit:hover { background: rgba(255, 255, 255, .025); border-radius: 4px; }

/* the old static strip, kept for the one chart that still prints a hint */
.nx-tip { margin-top: .5rem; font-size: .6875rem; color: var(--muted); }
.nx-tip-hint { color: var(--muted); font-size: .6875rem; }

/* breakdown list under a donut: name, share, exact value. Never colour alone. */
.nx-break { display: flex; flex-direction: column; margin-top: .75rem; }
.nx-break-row {
  display: flex; align-items: center; gap: .5rem; padding: .3rem 0;
  box-shadow: inset 0 -1px 0 rgba(35, 37, 42, .55); font-size: .75rem;
}
.nx-break-row:last-child { box-shadow: none; }
.nx-break-name { flex: 1; min-width: 0; color: var(--mist); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nx-break-pct { font-family: var(--mono); color: var(--muted); font-size: .6875rem; }
.nx-break-val { font-family: var(--mono); color: var(--paper); min-width: 5.5ch; text-align: right; }

/* what a donut becomes when it only ever had one category to show */
.nx-onecat { display: flex; flex-direction: column; gap: .2rem; padding: .875rem 0; }
.nx-onecat-v { font-family: var(--mono); font-size: 1.5rem; line-height: 1; color: var(--paper); }
.nx-onecat-l { font-size: .8125rem; color: var(--mist); }
.nx-onecat-n { font-size: .6875rem; color: var(--muted); }

/* funnel: the drop between two steps is the information, so it gets a line */
.nx-funnel-step { margin-bottom: .125rem; }
.nx-funnel-head { display: flex; align-items: baseline; gap: .5rem; font-size: .75rem; }
.nx-funnel-name { flex: 1; color: var(--mist); }
.nx-funnel-n { font-family: var(--mono); color: var(--paper); }
.nx-funnel-pct { font-family: var(--mono); font-size: .6875rem; color: var(--muted); min-width: 5ch; text-align: right; }
.nx-funnel-bar { height: 16px; border-radius: 3px; background: var(--graphite); overflow: hidden; margin-top: .25rem; }
.nx-funnel-fill { height: 100%; border-radius: 3px; background: var(--s1); }
.nx-funnel-fill.last { background: var(--s3); }
.nx-funnel-drop {
  font-family: var(--mono); font-size: .625rem; color: var(--muted);
  padding: .25rem 0 .25rem .875rem;
}
.nx-funnel-drop.bad { color: var(--coral); }

/* heatmap */
.nx-heat { display: grid; gap: 2px; }
.nx-heat-cell { border-radius: 2px; background: var(--graphite); min-height: 14px; }
.nx-heat-scale { display: flex; align-items: center; gap: .3rem; font-size: .625rem; color: var(--muted); margin-top: .5rem; justify-content: flex-end; }
.nx-heat-swatch { width: 16px; height: 9px; border-radius: 2px; }

/* bullet: a value against its benchmark, on one line */
.nx-bullet { display: flex; align-items: center; gap: .625rem; padding: .3rem 0; font-size: .75rem; }
.nx-bullet-name { min-width: 9ch; color: var(--mist); }
.nx-bullet-track { flex: 1; height: 5px; border-radius: 9999px; background: var(--graphite); position: relative; }
.nx-bullet-fill { height: 100%; border-radius: 9999px; background: var(--s1); }
.nx-bullet-mark { position: absolute; top: -3px; width: 1.5px; height: 11px; background: var(--mist); opacity: .8; }
.nx-bullet-val { font-family: var(--mono); color: var(--paper); min-width: 5ch; text-align: right; }

/* ==========================================================================
   live pulse. The only place the accent animates.
   ========================================================================== */

.nx-live { display: inline-flex; align-items: center; gap: .4rem; font-size: .75rem; color: var(--muted); }
.nx-live-dot { width: 6px; height: 6px; border-radius: 9999px; background: var(--toxic); position: relative; }
.nx-live-dot::after {
  content: ''; position: absolute; inset: -3px; border-radius: 9999px;
  background: var(--toxic); opacity: .35; animation: nxPulse 2.4s ease-out infinite;
}
@keyframes nxPulse {
  0% { transform: scale(.6); opacity: .45; }
  70% { transform: scale(1.6); opacity: 0; }
  100% { opacity: 0; }
}

/* ==========================================================================
   states
   ========================================================================== */

.empty-state { padding: 2.5rem 1rem; color: var(--muted); }
.empty-state svg { color: var(--graphite-hi); }
.empty-state p { font-size: .8125rem; }

.spinner {
  border-color: var(--graphite); border-top-color: var(--toxic);
  width: 18px; height: 18px; border-width: 2px;
}

/* skeleton for a KPI strip that has not answered yet */
.nx-skel { background: var(--carbon); animation: nxSkel 1.4s ease-in-out infinite; }
@keyframes nxSkel { 0%, 100% { opacity: 1; } 50% { opacity: .45; } }

/* ==========================================================================
   the legacy sheet hardcoded these; they have to be overruled by name
   ========================================================================== */

.card:hover, .gd-panel:hover { transform: none; box-shadow: var(--hair); }
[class*='card']:hover { box-shadow: var(--hair); }

.json-viewer, pre, code {
  font-family: var(--mono); font-size: .75rem; background: var(--void);
  box-shadow: var(--hair); border: 0; border-radius: var(--r-ui); color: var(--mist);
}

.bs-slot { box-shadow: var(--hair); border: 0; background: var(--carbon); }

.save-tree-label { background: var(--void); border: 0; box-shadow: inset 0 0 0 1px var(--edge); }
.save-tree-label:hover { border-color: transparent; background: var(--obsidian); }
.save-tree-row .st-key { color: var(--toxic-dim); }

/* support inbox */
.sup-msg-bot { background: rgba(39, 166, 68, .10); }
.sup-msg-admin { background: rgba(57, 135, 229, .10); }
.sup-msg-player { background: var(--obsidian); }
.sup-premium { color: var(--amber); }

/* ==========================================================================
   rarity. Carried over from the skin NEXUS replaces: this is the one place in
   the console where colour is functional rather than decorative, because the
   game itself teaches the player what legendary looks like.
   ========================================================================== */

:root {
  --rar-common: #8A8F98;
  --rar-rare: #3987E5;
  --rar-epic: #9085E9;
  --rar-legendary: #F2A33C;
}

.rarity-common { --rar: var(--rar-common); }
.rarity-rare { --rar: var(--rar-rare); }
.rarity-epic { --rar: var(--rar-epic); }
.rarity-legendary { --rar: var(--rar-legendary); }

.rar-badge {
  display: inline-flex; align-items: center; gap: .35em;
  font-family: var(--mono); font-size: .5625rem; text-transform: uppercase;
  letter-spacing: var(--track); font-weight: 500; padding: .2em .55em; border-radius: 4px;
  color: var(--rar, var(--muted));
  background: color-mix(in srgb, var(--rar, var(--muted)) 14%, transparent);
  border: 0; box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--rar, var(--muted)) 34%, transparent);
}
.rar-dot { display: inline-block; width: .5em; height: .5em; border-radius: 50%; background: var(--rar, var(--muted)); }

/* ==========================================================================
   modal, login, avatar
   ========================================================================== */

/*
  Modals get the same treatment as the glass readout, and for the same reason:
  a dialog is not part of the page, it is on top of it. The old one had a 4px
  blur behind a 60% black wash and a 1px border, which left the dialog and the
  page at roughly the same visual distance, so the eye had to work out which
  layer it was reading.

  What changed: the backdrop goes darker and blurrier so the page recedes
  properly, the dialog itself takes the glass treatment, the header and footer
  are hairline separated instead of bordered, and the whole thing arrives with
  a short scale so it reads as opening rather than appearing.
*/
.modal, .modal-content, .modal-card {
  background: rgba(15, 16, 17, .92);
  backdrop-filter: blur(20px) saturate(140%);
  -webkit-backdrop-filter: blur(20px) saturate(140%);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow: 0 24px 70px rgba(0, 0, 0, .6), inset 0 1px 0 rgba(255, 255, 255, .05);
  border-radius: 14px;
  animation: nxModalIn .16s cubic-bezier(.2, .7, .3, 1);
}
:root[data-theme='light'] .modal,
:root[data-theme='light'] .modal-content,
:root[data-theme='light'] .modal-card {
  background: rgba(255, 255, 255, .95);
  border-color: rgba(16, 19, 22, .10);
  box-shadow: 0 24px 70px rgba(16, 19, 22, .22), inset 0 1px 0 rgba(255, 255, 255, .8);
}

@keyframes nxModalIn {
  from { opacity: 0; transform: translateY(8px) scale(.985); }
  to { opacity: 1; transform: none; }
}

.modal-backdrop, .modal-overlay {
  background: rgba(4, 5, 6, .62);
  backdrop-filter: blur(8px) saturate(80%);
  -webkit-backdrop-filter: blur(8px) saturate(80%);
}

.modal-header {
  padding: .875rem 1.125rem;
  border-bottom: 0;
  box-shadow: inset 0 -1px 0 var(--graphite-hi);
  background: transparent;
  border-radius: 14px 14px 0 0;
}
.modal-header h2 { font-size: .9375rem; font-weight: 500; letter-spacing: var(--track); }
.modal-close {
  width: 28px; height: 28px; border-radius: var(--r-ui);
  color: var(--muted); background: transparent;
}
.modal-close:hover { background: var(--obsidian); color: var(--paper); }
.modal-body { padding: 1.125rem; }
.modal-footer {
  padding: .875rem 1.125rem; border-top: 0;
  box-shadow: inset 0 1px 0 var(--graphite-hi);
  gap: .5rem;
}

/* the modal scrolls, so its own bar should not look like the page's */
.modal::-webkit-scrollbar { width: 7px; }
.modal::-webkit-scrollbar-thumb { background: var(--graphite-hi); border-radius: 9999px; }

.login-card { background: var(--carbon); border: 0; box-shadow: var(--hair); border-radius: var(--r-card); }
.login-wrapper::before { background: radial-gradient(circle, rgba(108, 228, 24, .07) 0%, transparent 70%); }
.login-wrapper::after { background: radial-gradient(circle, rgba(108, 228, 24, .04) 0%, transparent 70%); }
.login-logo .game { color: var(--toxic); }

.admin-avatar { background: var(--toxic); color: var(--on-toxic); font-weight: 500; }
.admin-info .admin-role {
  background: transparent; color: var(--muted); box-shadow: inset 0 0 0 1px var(--graphite-hi);
  font-family: var(--mono); font-size: .5625rem; letter-spacing: var(--track);
}

/*
  The skin NEXUS replaces made --primary near white, so it needed a run of
  !important rules to stop white text landing on white buttons. The accent is
  a saturated green now and only the filled controls take dark ink; the pills
  and tabs are outlined, so they keep light ink and those overrides are gone.
*/
/*
  Anything filled with the accent takes dark ink. White on #6CE418 measures
  1.05:1, which is not low contrast, it is invisible: the Sign In button read
  as an empty green slab.
*/
/* La tinta de la accion primaria solo tiene sentido ENCIMA de la accion
   primaria. Sobre el boton de contorno daba 1,04:1, o sea invisible. */
.btn-primary,
button.primary:not(.secondary) {
  color: var(--on-toxic);
  font-weight: 500;
  letter-spacing: var(--track);
}
.btn-primary svg { stroke: var(--on-toxic); }
.btn-primary {
  background: var(--toxic); border: 0; border-radius: var(--r-ui);
  padding: .625rem; font-size: .875rem;
}
.btn-primary:hover { background: #7DF52E; box-shadow: none; }
.btn-primary:disabled { opacity: .45; }
.pagination-buttons button.active { background: var(--obsidian); color: var(--paper); box-shadow: inset 0 0 0 1px var(--edge); }

/*
  The legacy sheet fills the selected period pill and the selected analytics tab
  with --primary and writes --white on top. With the accent at #6CE418 that is
  white on bright green, which measures 1.05:1 and is simply unreadable. These
  controls are selected, not primary: they take the raised surface and a border,
  the same as every other selected thing in the console.
*/
.a-period-bar .ppill.active,
.period-pills .ppill.active,
.analytics-tabs .atab.active {
  background: var(--obsidian) !important;
  color: var(--paper) !important;
  box-shadow: inset 0 0 0 1px var(--edge) !important;
  text-shadow: none;
}
.analytics-tabs {
  background: transparent; border: 0; box-shadow: inset 0 0 0 1px var(--graphite);
  border-radius: var(--r-ui); padding: 3px; margin-bottom: 1rem;
}
.analytics-tabs .atab {
  text-transform: none; letter-spacing: var(--track); font-size: .75rem; font-weight: 500;
  border-radius: 4px; border-bottom: 0; padding: .375rem .75rem;
}
.analytics-tabs .atab.active { border-bottom: 0; }
.analytics-tabs .atab svg { width: 13px; height: 13px; opacity: .6; }
.analytics-tabs .atab.active svg { opacity: 1; }

.a-period-bar {
  display: flex; align-items: center; justify-content: space-between; gap: .75rem;
  flex-wrap: wrap; margin-bottom: .25rem;
}
.period-pills { display: flex; gap: 2px; }
.custom-range { display: flex; align-items: center; gap: .375rem; }
.refresh-btn {
  height: 30px; padding: 0 .625rem; font-size: .75rem; border-radius: var(--r-ui);
  background: transparent; color: var(--mist); border: 0;
  box-shadow: inset 0 0 0 1px var(--edge); display: inline-flex; align-items: center; gap: .375rem;
}
.refresh-btn:hover { background: var(--obsidian); color: var(--paper); }
.refresh-btn svg { width: 13px; height: 13px; }

/*
  --white stayed at #fff through both previous themes, which is why a checked
  toggle drew a white knob on a white track and read as off. The knob is the
  ground it sits on; the track is what says checked.
*/
input[type='checkbox'], input[type='radio'] { accent-color: var(--toxic); }
.toggle, .switch { background: var(--graphite); box-shadow: inset 0 0 0 1px var(--graphite-hi); }
.toggle.on, .switch.on, .toggle input:checked + span, .switch input:checked + span { background: var(--toxic); }
.toggle .knob, .switch .knob, .toggle-knob { background: var(--paper); }
.toggle.on .knob, .switch.on .knob, .toggle.on .toggle-knob { background: var(--void); }

/*
  Two rarity scales were fighting: literal hexes in the JS and the --rar-*
  tokens in CSS. The tokens win; the literals are neutralised where they land
  on a background or a border so a legendary item is one colour everywhere.
*/
[class*='rarity-'] { --rar-fallback: var(--muted); }

/* the Game Design screen shipped its own indigo tooltip theme */
.card-tip, .gd-tip, .showCardTip, [class*='-tip'] {
  background: var(--carbon) !important; border: 0 !important;
  box-shadow: var(--hair-hi) !important; color: var(--mist) !important;
  border-radius: var(--r-ui) !important;
}
.card-tip h4, .gd-tip h4 { color: var(--paper); }

@media (max-width: 900px) {
  .akpi-grid, .nx-kpirow { grid-template-columns: repeat(auto-fit, minmax(128px, 1fr)); }
  .akpi-value, .nx-kpi-value { font-size: 1.125rem; }
}

/* ==========================================================================
   player journey

   The control pill is the whole point of that screen: it says whether a value
   can be changed from here, needs a build, or needs a code change. Three
   states, three inks, and the tooltip spells each one out, because "servidor"
   and "codigo" look like categories and are actually a warning.
   ========================================================================== */

.nx-ctrl {
  display: inline-flex; align-items: center;
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); padding: .15em .5em; border-radius: 4px;
  white-space: nowrap; cursor: help;
}
.nx-ctrl-ok { color: var(--pulse); background: color-mix(in srgb, var(--pulse) 12%, transparent); }
.nx-ctrl-warn { color: var(--amber); background: color-mix(in srgb, var(--amber) 12%, transparent); }
.nx-ctrl-bad { color: var(--coral); background: color-mix(in srgb, var(--coral) 12%, transparent); }

/* where the value actually lives, under the sentence that describes it */
/* the file or id a value came from, under the sentence that names it */
.nx-src {
  display: block;
  font-family: var(--mono); font-size: .5938rem; color: var(--faint);
  margin-top: .2rem; word-break: break-word;
}

#sec-journey .table-wrap { padding: 0; }
#sec-journey table { font-size: .75rem; }
#sec-journey table td { vertical-align: top; padding-top: .5rem; padding-bottom: .5rem; }

/*
  Heatmap row label. It used to inherit the axis style at 9px inside a 34px
  gutter, which is fine for "Lun" and shreds a date into three fragments. The
  gutter is measured now, and the sample size drops to a second weight so the
  date keeps the line to itself.
*/
.nx-heat-row {
  display: flex; align-items: baseline; gap: .4rem;
  font-family: var(--mono); font-size: .625rem; line-height: 14px;
  color: var(--muted); white-space: nowrap; overflow: hidden;
}
.nx-heat-n { color: var(--faint); font-size: .5625rem; }

/* ==========================================================================
   popup gallery

   The renders come out of the Unity editor at the prefab's own aspect, which
   varies a lot: a full-screen battle pass next to a small confirm dialog. The
   tile fixes the frame and lets the art letterbox inside it, so the grid reads
   as a grid instead of a ragged pile.
   ========================================================================== */

/*
  Not the gap-over-a-background trick the KPI strips use. That one fills every
  cell of the grid, including the empty ones after the last tile, so a row of
  five popups in a six-column grid ended with a pale slab where nothing is.
  Tiles carry their own hairline and the grid keeps a real gutter.
*/
.nx-pop-grid {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(148px, 1fr));
  margin-bottom: 1rem;
}
.nx-pop {
  display: flex; flex-direction: column; gap: .375rem; align-items: stretch;
  background: var(--carbon); border: 0; box-shadow: var(--hair);
  border-radius: var(--r-card);
  padding: .625rem; cursor: pointer;
  text-align: left; font-family: var(--font);
  transition: background .12s ease, box-shadow .12s ease;
}
.nx-pop:hover { background: var(--obsidian); box-shadow: inset 0 0 0 1px var(--edge); }
.nx-pop-shot {
  display: flex; align-items: center; justify-content: center;
  height: 116px; border-radius: var(--r-ui); overflow: hidden;
  background: var(--void); box-shadow: var(--hair);
}
.nx-pop-shot img { max-width: 100%; max-height: 100%; object-fit: contain; display: block; }
.nx-pop-noshot { font-size: .625rem; color: var(--faint); font-family: var(--mono); }
.nx-pop-name {
  font-size: .75rem; color: var(--paper); letter-spacing: var(--track);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.nx-pop-meta {
  font-family: var(--mono); font-size: .5938rem; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* the detail dialog: art on the left at whatever size it wants, facts on the right */
.nx-pop-modal { max-width: 880px; width: 92%; }
.nx-pop-body { display: grid; grid-template-columns: minmax(0, 300px) minmax(0, 1fr); gap: 1.25rem; }
/*
  The figure hugs its art instead of stretching to the facts column. Popups run
  from a tall battle pass to a small confirm dialog, and a fixed box left half
  the panel as black nothing under the short ones.
*/
.nx-pop-figure {
  display: flex; align-items: flex-start; justify-content: center;
  align-self: start;
  background: var(--void); border-radius: var(--r-ui); box-shadow: var(--hair);
  padding: .75rem;
}
.nx-pop-figure img { max-width: 100%; max-height: 460px; object-fit: contain; display: block; }
.nx-pop-facts { min-width: 0; }
.nx-pop-row {
  display: grid; grid-template-columns: 132px minmax(0, 1fr); gap: .75rem;
  padding: .4rem 0; box-shadow: inset 0 -1px 0 rgba(35, 37, 42, .55); font-size: .75rem;
}
.nx-pop-k { color: var(--muted); }
.nx-pop-v { color: var(--mist); }
.nx-pop-ctrlnote { display: block; color: var(--muted); font-size: .6875rem; margin-top: .25rem; }

@media (max-width: 760px) {
  .nx-pop-body { grid-template-columns: 1fr; }
  .nx-pop-figure img { max-height: 300px; }
}

/* ==========================================================================
   the route

   A spine, not a table. The vertical rule and the numbered dots carry the
   order, so the eye reads "then, then, then" without anybody labelling the
   columns. Popups hang off the stop where they actually fire, and the tag on
   each one says the thing that matters commercially: whether it arrives on its
   own or waits to be tapped.
   ========================================================================== */

.nx-route {
  background: var(--carbon); box-shadow: var(--hair); border-radius: var(--r-card);
  padding: 1rem 1.125rem; margin-bottom: .75rem;
}
.nx-route-head { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.nx-route-head h3 { font-size: .875rem; font-weight: 500; }
.nx-route-warn {
  margin: .625rem 0 0; padding: .5rem .625rem; border-radius: var(--r-ui);
  background: color-mix(in srgb, var(--coral) 10%, transparent);
  color: var(--coral); font-size: .75rem; line-height: 1.5;
}

.nx-spine { list-style: none; margin: .875rem 0 0; padding: 0; position: relative; }
/* the rule that makes it a sequence instead of a stack */
.nx-spine::before {
  content: ''; position: absolute; left: 11px; top: 6px; bottom: 6px;
  width: 1px; background: var(--graphite);
}
.nx-stop { position: relative; display: flex; gap: .875rem; padding: 0 0 1.125rem; }
.nx-stop:last-child { padding-bottom: 0; }
.nx-stop-dot {
  position: relative; z-index: 1; flex-shrink: 0;
  width: 23px; height: 23px; border-radius: 9999px;
  background: var(--carbon); box-shadow: inset 0 0 0 1px var(--graphite-hi);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: .625rem; color: var(--muted);
}
.nx-stop.warn .nx-stop-dot { box-shadow: inset 0 0 0 1px var(--coral); color: var(--coral); }
.nx-stop.fork .nx-stop-dot { box-shadow: inset 0 0 0 1px var(--toxic); color: var(--toxic-ink); }
.nx-stop-body { min-width: 0; flex: 1; padding-top: .1rem; }
.nx-stop-t { font-size: .8125rem; color: var(--paper); }
.nx-stop-d { font-size: .75rem; color: var(--muted); margin-top: .1rem; line-height: 1.5; }
.nx-stop-ev { font-family: var(--mono); font-size: .5938rem; color: var(--faint); margin-top: .25rem; }
.nx-stop-none { font-size: .6875rem; color: var(--faint); margin-top: .375rem; font-style: italic; }

.nx-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: .5rem; }
.nx-chip {
  display: flex; align-items: center; gap: .5rem; padding: .3rem .5rem .3rem .3rem;
  background: var(--obsidian); border: 0; box-shadow: var(--hair-hi);
  border-radius: 8px; cursor: pointer; text-align: left;
  font-family: var(--font); max-width: 300px;
  transition: box-shadow .12s ease, background .12s ease;
}
.nx-chip:hover { background: var(--carbon); box-shadow: inset 0 0 0 1px var(--edge); }
.nx-chip[disabled] { opacity: .5; cursor: default; }
.nx-chip-shot {
  width: 30px; height: 30px; flex-shrink: 0; border-radius: 5px; overflow: hidden;
  background: var(--void); display: flex; align-items: center; justify-content: center;
}
.nx-chip-shot img { max-width: 100%; max-height: 100%; object-fit: contain; }
.nx-chip-txt { min-width: 0; display: flex; flex-direction: column; }
.nx-chip-n { font-size: .6875rem; color: var(--paper); white-space: nowrap; }
.nx-chip-w {
  font-size: .5938rem; color: var(--muted); white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis; max-width: 190px;
}
.nx-chip-tag, .nx-chip-legend {
  font-family: var(--mono); font-size: .5312rem; text-transform: uppercase;
  letter-spacing: var(--track); padding: .12em .4em; border-radius: 3px;
  color: var(--muted); background: color-mix(in srgb, var(--muted) 14%, transparent);
  white-space: nowrap;
}
/* fires by itself: the only ones the studio actually controls the timing of */
.nx-chip.auto .nx-chip-tag, .nx-chip-legend.auto {
  color: var(--toxic-ink); background: var(--toxic-wash);
}
.nx-chip.hard .nx-chip-tag { color: var(--coral); background: color-mix(in srgb, var(--coral) 14%, transparent); }

/* the unlock ladder */
.nx-ladder { display: flex; flex-direction: column; gap: 1px; margin-top: .875rem; }
.nx-rung {
  display: grid; grid-template-columns: 190px minmax(0, 1fr); gap: .875rem;
  padding: .5rem 0; box-shadow: inset 0 -1px 0 rgba(35, 37, 42, .55); align-items: baseline;
}
.nx-rung-at {
  font-family: var(--mono); font-size: .625rem; letter-spacing: var(--track);
  padding: .15em .5em; border-radius: 4px; justify-self: start;
}
.nx-rung-cap { color: var(--s1); background: color-mix(in srgb, var(--s1) 14%, transparent); }
.nx-rung-nivel { color: var(--s3); background: color-mix(in srgb, var(--s3) 14%, transparent); }
.nx-rung-paso { color: var(--s4); background: color-mix(in srgb, var(--s4) 14%, transparent); }
.nx-rung-evento { color: var(--s7); background: color-mix(in srgb, var(--s7) 14%, transparent); }
.nx-rung-items { display: flex; flex-wrap: wrap; gap: .3rem .5rem; }
.nx-rung-item { font-size: .75rem; color: var(--mist); }
.nx-rung-item:not(:last-child)::after { content: ' ·'; color: var(--faint); }

@media (max-width: 760px) {
  .nx-rung { grid-template-columns: 1fr; gap: .3rem; }
  .nx-chip { max-width: 100%; }
}

/* ==========================================================================
   hover preview

   The same glass treatment as the chart readout, for the same reason: it
   floats over content it cannot predict, so it needs the blur to separate
   itself and the shadow to say how far above it sits. Pointer-events off, or
   it would chase itself around the screen.
   ========================================================================== */

.nx-imgtip {
  position: fixed; z-index: 9998; display: none; pointer-events: none;
  padding: .5rem; border-radius: 10px;
  background: rgba(15, 16, 17, .90);
  backdrop-filter: blur(14px) saturate(150%);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  border: 1px solid rgba(255, 255, 255, .10);
  box-shadow: 0 14px 40px rgba(0, 0, 0, .58);
  animation: nxTipIn .1s ease-out;
}
:root[data-theme='light'] .nx-imgtip {
  background: rgba(255, 255, 255, .93);
  border-color: rgba(16, 19, 22, .10);
  box-shadow: 0 14px 40px rgba(16, 19, 22, .18);
}
.nx-imgtip img {
  display: block; max-width: 260px; max-height: 380px;
  border-radius: 6px; background: var(--void);
}
.nx-imgtip-cap {
  font-size: .6875rem; color: var(--paper); margin-top: .4rem;
  max-width: 260px; line-height: 1.4;
}
.nx-imgtip-note { display: block; color: var(--muted); font-size: .625rem; margin-top: .15rem; }

/*
  A name you can hover is marked as one. A dotted underline rather than a solid
  link: it opens a picture, it does not go anywhere.
*/
.nx-hasart {
  text-decoration: underline dotted var(--graphite-hi);
  text-underline-offset: 3px;
}
.nx-hasart:hover { text-decoration-color: var(--edge); color: var(--paper); }

/* ── Fila etiqueta/valor dentro de un modal ──────────────────────────────
   Los dos lados se leen en columna: la etiqueta a la izquierda en texto
   normal, el numero a la derecha en la misma cifra tabular que las tablas,
   para que comparar 1 contra 0 no dependa del ancho de la fuente. */
.nx-kv {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--border);
  font-size: .8125rem;
}
.nx-kv:last-child { border-bottom: 0; }
.nx-kv > span { color: var(--muted); }
.nx-kv > b {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-weight: 500;
  color: var(--paper);
}

/* Boton de una accion que el servidor ya ha desaconsejado. No es rojo de
   alarma: es rojo de "esto lo estas eligiendo tu". */
.filter-btn.danger {
  background: transparent;
  color: var(--danger-ink);
  border: 0;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--danger-ink) 55%, transparent);
}
.filter-btn.danger:hover {
  background: color-mix(in srgb, var(--danger-ink) 12%, transparent);
  box-shadow: inset 0 0 0 1px var(--danger-ink);
  color: var(--danger-ink);
}
.filter-btn.danger svg { stroke: var(--danger-ink); }

/* ==========================================================================
   Monitoring: las piezas de una consola de observabilidad

   Lo que separa un panel de estado de una tabla con colores es que responde a
   tres preguntas de un vistazo: como esta AHORA, como ha ido HASTA AHORA, y
   cuanto tarda cuando va mal. Historial por servicio, percentiles en vez de
   media, y el rojo como raíl fino con su palabra al lado en vez de un bloque
   que se come la pantalla: el color no puede ser el unico indicador.
   ========================================================================== */

/* El veredicto. Una sola frase, grande, sin adorno. */
.nx-verdict {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 1.5rem; flex-wrap: wrap;
  padding: 1.25rem 1.375rem;
  border-bottom: 1px solid var(--border);
}
.nx-verdict-headline {
  font-size: 1.375rem; font-weight: 500; letter-spacing: var(--track);
  line-height: 1.15; color: var(--paper);
}
/* el titular se lee en tinta principal; la gravedad la marca el punto */
.nx-verdict-sub {
  margin-top: .3rem; font-size: .8125rem; color: var(--muted); line-height: 1.5;
  max-width: 46rem;
}
.nx-verdict-meta {
  display: flex; align-items: center; gap: 1.25rem;
  font-family: var(--mono); font-size: .6875rem; color: var(--faint);
  font-variant-numeric: tabular-nums;
}
.nx-verdict-meta b { color: var(--mist); font-weight: 500; }

/* La fila de servicio. El raíl de la izquierda es lo unico que cambia de color. */
.nx-svc {
  display: grid;
  grid-template-columns: minmax(11rem, 1.4fr) minmax(7rem, 10rem) 4.5rem 5.5rem;
  align-items: center; gap: 1rem;
  padding: .5rem .75rem .5rem .625rem;
  border-left: 2px solid transparent;
  transition: background-color 160ms ease;
}
.nx-svc + .nx-svc { border-top: 1px solid var(--border); }
.nx-svc:hover { background: var(--obsidian); }
.nx-svc.is-down { border-left-color: var(--danger-ink); }
.nx-svc.is-up { border-left-color: color-mix(in srgb, var(--s3) 40%, transparent); }

.nx-svc-name { font-size: .8125rem; color: var(--paper); letter-spacing: var(--track); }
.nx-svc-note { font-size: .6875rem; color: var(--faint); margin-top: .1rem; line-height: 1.4; }

/* El estado, en palabra. Sin pastilla rellena. */
.nx-svc-state {
  font-family: var(--mono); font-size: .625rem; text-transform: uppercase;
  letter-spacing: var(--track); font-variant-numeric: tabular-nums;
}
.nx-svc-state.up { color: var(--s3); }
.nx-svc-state.down { color: var(--danger-ink); }

/* Historial: una barra por comprobacion, la mas reciente a la derecha. */
.nx-spark { display: flex; align-items: flex-end; gap: 1px; height: 20px; }
.nx-spark i {
  flex: 1 1 auto; min-width: 2px; border-radius: 1px;
  background: color-mix(in srgb, var(--s3) 55%, transparent);
}
.nx-spark i.down { background: var(--danger-ink); }
.nx-spark i.gap  { background: var(--graphite); }

.nx-svc-num {
  font-family: var(--mono); font-size: .75rem; text-align: right;
  font-variant-numeric: tabular-nums; color: var(--mist);
}
.nx-svc-num.dim { color: var(--faint); }

/* Cabecera de columna: micro, y solo una vez. */
.nx-svc-head {
  display: grid;
  grid-template-columns: minmax(11rem, 1.4fr) minmax(7rem, 10rem) 4.5rem 5.5rem;
  gap: 1rem; padding: 0 .75rem .35rem .625rem;
  border-left: 2px solid transparent;
  font-family: var(--mono); font-size: .5625rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--faint);
}
.nx-svc-head span:nth-child(3), .nx-svc-head span:nth-child(4) { text-align: right; }

/* Etiqueta de grupo. Sin regla, que ya la dibujan las filas. */
.nx-svc-group {
  display: flex; align-items: baseline; gap: .6rem;
  padding: 1.125rem .625rem .3rem;
}
.nx-svc-group-name {
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--muted);
}
.nx-svc-group-hint { font-size: .6875rem; color: var(--faint); }

/* La cuenta atras hasta la siguiente comprobacion. */
.nx-tick { display: inline-flex; align-items: center; gap: .4rem; }
.nx-tick-dot {
  width: 5px; height: 5px; border-radius: 50%; background: var(--s3);
  animation: nx-pulse 2s ease-in-out infinite;
}
@keyframes nx-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) {
  .nx-tick-dot { animation: none }
  .nx-svc { transition: none }
}

@media (max-width: 760px) {
  .nx-svc, .nx-svc-head { grid-template-columns: 1fr 4.5rem 5rem; }
  .nx-svc .nx-spark, .nx-svc-head span:nth-child(2) { display: none; }
}

/* Un veredicto de una linea, para las paginas que no son el Dashboard. */
.nx-alerts-strip {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .5rem .875rem; margin-bottom: .75rem;
  border: 1px solid var(--border); border-radius: var(--r-ui);
  background: var(--carbon);
  font-family: var(--mono); font-size: .6875rem;
}
.nx-alerts-strip-label {
  text-transform: uppercase; letter-spacing: var(--track); font-size: .5938rem; color: var(--faint);
}
.nx-alerts-strip-sep { color: var(--graphite-hi); }
.nx-alerts-strip-go {
  margin-left: auto; background: none; border: 0; cursor: pointer; padding: 0;
  font-family: var(--mono); font-size: .6875rem; color: var(--toxic-ink);
  text-decoration: underline; text-underline-offset: 3px;
}
.nx-alerts-strip-go:hover { color: var(--paper); }


/* Cabecera de tabla pegajosa: una tabla larga sin encabezado visible obliga a
   subir para saber que columna se esta leyendo. Necesita fondo propio, si no
   las filas se ven por debajo. */
.table-wrap { max-height: 62vh; overflow: auto; }
table thead th {
  position: sticky; top: 0; z-index: 2;
  background: var(--carbon);
  box-shadow: inset 0 -1px 0 var(--border);
}

/* La inicial cuando falta el png de la mascota. */
.nx-pet-fallback {
  width: 64px; height: 64px; border-radius: 12px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-weight: 600; font-size: 1.4rem;
  color: var(--rar); background: color-mix(in srgb, var(--rar) 13%, transparent);
  border: 1px solid color-mix(in srgb, var(--rar) 30%, transparent);
}

/* La etiqueta que encuadra una cohorte pequena. Va ANTES de las cifras: quien
   lee primero el numero y despues la muestra ya se hizo una idea equivocada. */
.nx-cohort-label {
  font-size: .75rem; color: var(--muted); line-height: 1.5;
  padding: 0 .125rem .5rem;
}

/* Cabecera de pagina degradada a etiqueta: el titulo ya esta en la barra
   lateral, y competia con el veredicto por el mismo peso optico. */
/* La regla de is-label vive ahora junto a .page-header, arriba. */

/* "Qué mirar hoy" plegable. La cabecera entera es el disparador, que es un
   objetivo mucho mayor que una flecha de 12px. */
.nx-alerts-head[data-nx-toggle] { cursor: pointer; user-select: none; }
.nx-alerts-head[data-nx-toggle]:hover h2 { color: var(--paper); }
.nx-alerts-head[data-nx-toggle]:focus-visible {
  outline: 2px solid var(--toxic-ink); outline-offset: 2px; border-radius: var(--r-ui);
}
.nx-alerts-caret {
  display: inline-block; width: 0; height: 0; margin-right: .5rem;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid var(--muted);
  transform: rotate(0deg); transition: transform 160ms ease;
  vertical-align: middle;
}
.nx-alerts-caret.is-closed { transform: rotate(-90deg); }
.nx-alerts.is-collapsed .nx-alerts-body { display: none; }
.nx-alerts.is-collapsed .nx-alerts-head { border-bottom: 0; }
@media (prefers-reduced-motion: reduce) { .nx-alerts-caret { transition: none } }


/* ==========================================================================
   Foco visible. Una consola se recorre con el teclado, y hasta ahora el foco
   se lo comia el navegador con su anillo por defecto sobre un fondo negro.
   ========================================================================== */
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--toxic);
  outline-offset: 2px;
  border-radius: var(--r-ui);
}
:root[data-theme='light'] :where(a, button, input, select, textarea, [tabindex]):focus-visible {
  outline-color: var(--toxic-ink);
}

/* La fila de tabla y la tarjeta responden al raton con el escalon de arriba,
   no con un gris cualquiera: mismo lenguaje de elevacion en todas partes. */
.card:hover, .achart-card:hover { background: var(--obsidian); }

/* ==========================================================================
   Barra superior

   El marco que le faltaba a la consola. Dice donde estas, contra que entorno
   hablas y si la API responde, que es informacion que solo existia enterrada
   al pie de la barra lateral: justo donde nadie mira cuando algo va mal.
   ========================================================================== */
.topbar {
  position: sticky; top: 0; z-index: 40;
  margin-left: var(--sidebar-w);
  height: 46px; display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; padding: 0 1.5rem;
  background: color-mix(in srgb, var(--void) 88%, transparent);
  backdrop-filter: saturate(1.2) blur(8px);
  box-shadow: inset 0 -1px 0 var(--graphite);
}
.topbar-crumb {
  font-size: .8125rem; font-weight: 500; color: var(--paper); letter-spacing: var(--track);
}
.topbar-right { display: flex; align-items: center; gap: .875rem; }

/* El entorno, escrito. Una consola que puede hablar con produccion deberia
   decirlo siempre, no solo cuando alguien se acuerda de mirar la URL. */
.topbar-env {
  font-family: var(--mono); font-size: .5938rem; text-transform: uppercase;
  letter-spacing: var(--track); color: var(--s4);
  padding: .15rem .45rem; border-radius: var(--r-ui);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--s4) 45%, transparent);
}
.topbar-api {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--mono); font-size: .6875rem; color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.topbar-theme {
  background: none; border: 0; cursor: pointer; padding: .25rem;
  color: var(--muted); display: inline-flex; border-radius: var(--r-ui);
}
.topbar-theme:hover { color: var(--paper); background: var(--obsidian); }

/* El contenido ya no empieza pegado al techo: la barra ocupa su sitio. */
.main { padding-top: 1.125rem; }

@media (max-width: 900px) {
  .topbar { margin-left: 0; padding: 0 1rem; }
  .topbar-env { display: none; }
}

/* Una nota que se repite en varias tarjetas de la misma pantalla. Se dice, pero
   en voz baja: si compite con el dato deja de ser una aclaracion y pasa a ser
   ruido. El motivo completo va en el title. */
.nx-note-quiet {
  font-family: var(--mono); font-size: .625rem; color: var(--faint);
  margin-top: .5rem; cursor: help;
}

/* ==========================================================================
   BASEDASH · CAPA DE COMPONENTE
   ========================================================================== */

/*
  El tracking. -0.03em en TODO el texto de Inter, del tamano que sea. El spec
  lo pone como voz del sistema y no como ajuste optico de un tamano concreto,
  asi que va en la raiz y hereda.
*/
body, button, input, select, textarea, table { letter-spacing: var(--track); }
h1, h2, h3, h4, h5, h6 { letter-spacing: var(--track); }

/*
  El serif es la firma editorial y esta reservado al titulo. El spec lo fija a
  48px con line-height 1 y prohibe escalarlo o cambiarle el peso. Aqui el
  titulo de pagina vive en una barra superior de 46px, asi que va a 30px, que
  es el escalon heading-sm de la propia escala del spec.
*/
.page-header h1, .topbar-crumb, .login-logo .logo-text {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0;
  color: var(--paper);
}
.page-header h1 { font-size: 30px; line-height: 1.2; }

/*
  La accion primaria es BLANCA sobre negro. Esa inversion es la firma del
  sistema: el objeto mas brillante de la pantalla es la unica cosa que hay que
  pulsar. Radio 6, que en este sistema no se cruza con el 16 de la tarjeta.
*/
.btn-primary, button.btn-primary {
  background: var(--ember); color: var(--on-ember);
  border: 0; border-radius: var(--r-ui); font-weight: 500;
}
.btn-primary:hover { background: var(--toxic-dim); }
.btn-primary svg { stroke: var(--on-ember); }

/* La accion secundaria es contorno, y el borde cumple 1.4.11 por --edge. */
.filter-btn, .filter-btn.secondary, .refresh-btn {
  background: transparent; color: var(--paper); font-weight: 500;
  border: 0; border-radius: var(--r-ui);
  box-shadow: inset 0 0 0 1px var(--edge);
}
.filter-btn:hover, .refresh-btn:hover { box-shadow: inset 0 0 0 1px var(--paper); }

/* 6 en lo operable, 16 en el contenedor, 999 en la chapa. No se interpolan. */
button, .btn, .seg-pill, .ppill, .filter-pill, input, select, textarea,
.search-input, .form-input, .tab, .atab, .nx-viewtab, .nav-item, .nx-chip {
  border-radius: var(--r-ui);
}
.badge, .rar-badge, .nav-badge, .nx-ctrl, .status-dot, .api-dot { border-radius: var(--r-pill); }
.card, .achart-card, .gd-panel, .cb-group, .stat-card, .table-wrap,
.nx-alerts, .login-card, .nx-pop, .nx-route, .modal, .modal-content, .modal-card {
  border-radius: var(--r-card);
}
pre, code, .json-viewer, .stacked-bar, .nx-funnel-bar, .nx-funnel-fill,
.nx-heat-cell, .nx-bullet-track, .nx-bullet-fill { border-radius: var(--r-small); }

/*
  El spec deja la tarjeta a 1,036:1 sobre el lienzo a proposito, para que se
  lea "carved into the void". En una landing funciona. Aqui hay 264 tarjetas de
  datos y sin borde sencillamente no existen, asi que llevan el hairline de
  Graphite que el propio spec define como "deep borders".
*/
.card, .achart-card, .gd-panel, .cb-group, .stat-card, .table-wrap,
.nx-alerts, .login-card, .akpi-grid, .nx-kpirow, .nx-verdict {
  box-shadow: var(--hair);
}

/* Micro-etiqueta. Basedash no tiene versalitas ni mono: la etiqueta de una
   tarjeta de metrica es Inter en Ash, y el contraste con su cifra lo hace el
   tamano y el peso, no la caja alta. */
.nav-section, .stat-card .stat-label, .akpi-label, .nx-kpi-label,
.nx-band-label, .a-section-title, table thead th, .nx-svc-group-name,
.topbar-env, .achart-meta, .pagination-info, .nx-alerts-strip-label {
  font-family: var(--font);
  text-transform: none;
  letter-spacing: var(--track);
  font-size: 12px;
  font-weight: 400;
  color: var(--mist);
}

/* La cifra: Inter 34 peso 600 en blanco, que es la receta del spec. */
.akpi-value, .nx-kpi-value, .stat-card .stat-value {
  font-family: var(--font); font-weight: 500; color: var(--paper);
  letter-spacing: var(--track);
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: var(--numerals);
}

/* Todo lo que lleva cifras, con figuras tabulares y el kerning apagado. */
.mono, .nx-num, table td, table th, .nx-delta, .nx-onecat-v, .nx-break-val,
.nx-funnel-n, .nx-bullet-val, .nx-alerts-counts, .nx-verdict-meta,
.nx-svc-now, .nx-svc-p95, .topbar-api {
  font-variant-numeric: tabular-nums lining-nums;
  font-feature-settings: var(--numerals);
}

/* Pesos: 400, 500 y 600. El enfasis es color y tamano, no grosor. */
b, strong { font-weight: 500; }

/* El foco. El spec no lo define; 2.4.13 exige grosor y area, y en un sistema
   blanco y negro el anillo mas legible es el blanco. */
*:focus-visible { outline: 2px solid var(--paper); outline-offset: 2px; }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; box-shadow: inset 0 0 0 2px var(--paper);
}
::selection { background: var(--paper); color: var(--void); }
input[type=checkbox], input[type=radio] { accent-color: var(--paper); }

/* Navegacion activa: en un sistema sin color, "estas aqui" es contraste. */
.nav-item.active { background: var(--obsidian); color: var(--paper); }
.nav-item.active::before { background: var(--paper); }
.tab.active, .atab.active, .nx-viewtab.active { border-bottom-color: var(--paper); }

/* Estado en vivo: punto menta con su etiqueta, que es la receta del spec. */
.nx-live-dot, .nx-live-dot::after { background: var(--mint); }
.spinner { border-top-color: var(--mist); }

/* Cero sombras. Las siete reales del sistema anterior se apagan; los anillos
   inset NO son sombras, son los bordes, y se quedan. */
.nx-glasstip, .nx-imgtip, .modal, .modal-content, .modal-card {
  box-shadow: var(--hair);
  backdrop-filter: none;
}
.api-dot, .status-dot, .sup-av, .nx-alert-dot, .nx-legend-dot, .nx-stop-dot {
  box-shadow: none;
}

/* El cian de "ok" daba 3,06:1 como TEXTO sobre la tarjeta clara. Un color de
   serie se valida como objeto grafico (3:1); como texto pide 4,5:1. */
.nx-svc-state.up, .nx-state-ok { color: var(--s3-ink); }
.nx-svc-state.down, .nx-state-bad { color: var(--s2-ink); }

/* Las chapas de dato ponen el color de serie como TEXTO sobre su propio lavado,
   que es el fondo donde peor contrasta. Van a tinta, no al relleno. */
.badge-info, .badge.badge-info { color: var(--s1-ink); }
.badge-orange, .badge.badge-orange { color: var(--s4-ink); }
.badge-cyan, .badge.badge-cyan { color: var(--s3-ink); }
.badge-pink, .badge.badge-pink { color: var(--s6-ink); }
.badge-purple, .badge.badge-purple { color: var(--s5-ink); }
.badge-success, .badge.badge-success { color: var(--s8-ink); }

/* ==========================================================================
   EL COLOR ES UN PUNTO, NO EL TEXTO

   En un sistema blanco y negro, tenir de rojo un titular de 23px y tres cifras
   de 30px es meter color justo donde el spec lo prohibe ("reserve the violet
   and mint for data and live status, never apply them to text"). Y ademas es
   peor informacion: una cifra que hay que LEER no se tine; el punto avisa.
   De paso el color deja de ser el unico portador del significado, que es 1.4.1.
   ========================================================================== */

.nx-verdict-headline, .nx-verdict.is-down .nx-verdict-headline {
  color: var(--paper);
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: 0;
}
.nx-verdict.is-down .nx-verdict-headline::before {
  content: ''; display: inline-block; width: 8px; height: 8px;
  border-radius: 50%; background: var(--s2); margin-right: 10px;
  vertical-align: middle;
}

.nx-kpi-value.bad, .akpi-value.bad, .nx-kpi-value.warn, .akpi-value.warn {
  color: var(--paper);
}
.nx-kpi-value.bad::before, .akpi-value.bad::before,
.nx-kpi-value.warn::before, .akpi-value.warn::before {
  content: ''; display: inline-block; width: 7px; height: 7px;
  border-radius: 50%; margin-right: 8px; vertical-align: middle;
}
.nx-kpi-value.bad::before, .akpi-value.bad::before { background: var(--s2); }
.nx-kpi-value.warn::before, .akpi-value.warn::before { background: var(--s4); }

/* La navegacion es mas alta que su caja desde siempre (931px de contenido en
   737 de hueco), asi que el ultimo item se corta por la mitad y se lee como
   roto en vez de como "hay mas abajo". */
.sidebar-nav {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
}
.sidebar-nav:hover { -webkit-mask-image: none; mask-image: none; }

/* ==========================================================================
   UNA SOLA FUENTE

   El serif de titular era la firma de Basedash, pero en una consola de datos
   el titulo no vive solo en una portada: aparece en el veredicto de cada
   pantalla, en la barra superior y en las cabeceras, y ahi mezclar dos caras
   se lee como descuido, no como acento editorial. Inter lo hace todo.
   ========================================================================== */

.page-header h1, .topbar-crumb, .login-logo .logo-text,
.nx-verdict-headline, .nx-verdict.is-down .nx-verdict-headline,
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font);
  letter-spacing: var(--track);
}
.page-header h1 { font-weight: 600; }
.nx-verdict-headline, .nx-verdict.is-down .nx-verdict-headline { font-weight: 600; }

/* ==========================================================================
   LA NAVEGACION ACTIVA, SIN LA BARRA VERTICAL

   La barra de borde a borde a la izquierda era un objeto mas en pantalla para
   decir algo que la superficie y la tinta ya dicen. Fuera. El item activo se
   marca por contraste, que es como lo resuelve un sistema sin color: fondo
   elevado, tinta blanca y el icono al mismo blanco.
   ========================================================================== */

.nav-item.active::before { display: none; content: none; }
.nav-item.active {
  background: var(--obsidian);
  color: var(--paper);
  box-shadow: inset 0 0 0 1px var(--graphite);
}
.nav-item.active svg { stroke: var(--paper); opacity: 1; }
.nav-item:hover:not(.active) { background: color-mix(in srgb, var(--obsidian) 55%, transparent); }

/* ==========================================================================
   LIQUID METAL

   El canvas va DEBAJO del contenido del boton, recortado por el propio radio.
   El boton conserva su relleno blanco por debajo: si no hay WebGL, o si el
   contexto se pierde, lo que queda es exactamente el boton de antes, que ya
   cumple AA. El metal es una capa encima, no el suelo.
   ========================================================================== */

.btn-primary { position: relative; overflow: hidden; isolation: isolate; }
.btn-primary > .nx-liquid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; pointer-events: none;
  border-radius: inherit;
}
.btn-primary.has-liquid > *:not(.nx-liquid) { position: relative; z-index: 1; }
/* La tinta del boton sigue siendo negra sobre metal claro: el shader se mueve
   entre 0,55 y 1,0 de luminancia, asi que el negro encima aguanta AA en todo
   el recorrido. */
.btn-primary.has-liquid {
  position: relative;
  display: block; margin: 1.25rem auto 0;
  border: 0;
  overflow: visible;
}

@media (prefers-reduced-motion: reduce) {
  /* el metal se queda, pero quieto: el reloj se congela en el modulo */
  .btn-primary > .nx-liquid { opacity: .9; }
}

/* La etiqueta va por encima del metal. El modulo envuelve el nodo de texto al
   montar, porque un nodo de texto suelto no se puede subir de capa con CSS. */
.btn-primary > .nx-liquid-label { position: relative; z-index: 1; }
/* El metal se acota en el shader a la banda que deja legible su propia tinta:
   claro con negro encima (10,5:1 en el peor punto), oscuro con blanco (4,7:1). */
/*
  EL CUERPO DE LA PASTILLA, Y SUS ESQUINAS.

  Dos cosas en una regla, porque son la misma:

  1. El boton se queda con el mismo casi negro que el plate del documento
     (#0b0c0e). Si WebGL falla o el documento no llega, lo que queda es una
     pastilla oscura legible, no un boton invisible. La clase se pone antes de
     pedir el documento, asi que ese caso existe de verdad.

  2. Y con el MISMO radio. El plate es una pastilla de 999px y el boton tenia
     6px, asi que en las cuatro esquinas asomaba el rectangulo por detras del
     ovalo: esquinas redondeadas y puntiagudas a la vez en el mismo control.
*/
.btn-primary.has-liquid,
.filter-btn.has-liquid {
  background: #0b0c0e;
  border-radius: 999px;
  box-shadow: none;
}

/* La accion primaria de pantalla tambien lleva metal, asi que necesita el
   mismo andamiaje que .btn-primary. Los .secondary siguen de contorno. */
.filter-btn:not(.secondary) { position: relative; overflow: hidden; isolation: isolate; }
.filter-btn > .nx-liquid {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; z-index: 0; pointer-events: none; border-radius: inherit;
}
.filter-btn > .nx-liquid-label { position: relative; z-index: 1; }
.filter-btn.has-liquid:hover { background: #0b0c0e; }


/* ==========================================================================
   LIQUID METAL BUTTON

   El documento autorizado trae su propio escenario Y sus propias proporciones,
   y las dos cosas hay que respetarlas o se rompe.

   Con sus numeros: --h 52px, u = h/516, la pastilla mide pillWidthUnits * u de
   ancho (para "Sign In" son 820 + 7*94 = 1478 unidades) y el escenario anade
   900 unidades de sangrado por lado para que el bloom no se recorte. Sale una
   pastilla de 149x52 centrada en un lienzo de 330x233, y eso coincide al pixel
   con el canvas que sirve la referencia.

   Asi que el boton NO estira al componente: el boton toma el tamano que el
   componente da. Intente lo contrario (boton al 100% de ancho, iframe
   estirado) y salio una losa negra: el escenario se deformaba y solo se veia
   su fondo.

   Y el escenario es OSCURO por diseno, porque su sombra necesita algo sobre lo
   que caer. Por eso la tarjeta del login se oscurece: es la unica pantalla
   donde vive el componente, y asi lo ponemos en el contexto para el que esta
   hecho en vez de pegarlo sobre blanco.
   ========================================================================== */

/* ==========================================================================
   UNA SOLA REGLA, EN LOS DOS SITIOS

   El documento pone el METAL. El contenido lo pone siempre el boton: su
   etiqueta, su icono, su tipografia, su relleno y su ancho de siempre.

   Antes era al reves y por eso se veia mal. El modulo calculaba el ancho de la
   pastilla y se lo IMPONIA al boton con style.width, asi que un texto largo se
   quedaba sin sitio ("Guardar cambios" cortado en Remote Config) y, en el
   menu, una fila de 215px acababa con una pastilla de 122 centrada 46px a la
   izquierda: de ahi salia la etiqueta pegada al icono.

   Ahora el boton manda. El metal se mide de su caja y lo sigue con un
   ResizeObserver, asi que no hay ancho que se pueda quedar corto.
   ========================================================================== */

.btn-primary.has-liquid {
  position: relative;
  margin: 1.25rem auto 0;
  background: transparent;
  box-shadow: none;
  border: 0;
}
.btn-primary.has-liquid > .nx-liquid {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  border: 0; display: block; z-index: 0;
  pointer-events: auto;
  /* el tamano y el recorte los pone el modulo: salen de la caja del boton */
}
/* ==========================================================================
   POR QUE LA ETIQUETA LLEVA CERCO Y NO MEZCLA

   Primero la puse con mix-blend-mode: difference, que invierte el texto contra
   lo que tenga detras. Sobre negro sale blanco, sobre blanco sale negro, y
   medido en un fotograma daba 15:1. Parecia resuelto.

   No lo estaba. difference devuelve |255 - fondo|, asi que en MEDIO TONO
   devuelve el propio medio tono: fondo 128 da texto 127, que es 1,0:1. O sea
   que justo por el centro de su recorrido el texto desaparece, y el metal pasa
   por ahi continuamente. Medido sobre la pastilla con el raton encima: 1,94:1
   en la peor columna. Un fotograma bueno no es una medida.

   Con cerco el contraste deja de depender del shader: la referencia ya no es
   el metal, es el propio cerco. Blanco sobre negro son 21:1 se ponga como se
   ponga el fondo. Es la tecnica de siempre para texto sobre imagen, y aqui el
   fondo es literalmente una imagen que se mueve.
   ========================================================================== */

.has-liquid > .nx-liquid-label,
.nx-nav-pill + .nav-item.active,
html.has-nav-pill .nav-item.active {
  /* Sin contorno duro: cuatro sombras concentricas sin desplazar. Un cerco de
     1px a los cuatro lados tapaba el numero pero se leia como pegatina; asi es
     un pozo oscuro que envuelve el trazo, que es lo que hace el propio
     componente con su plate. Medido, el suavizado no cuesta contraste. */
  --nx-halo:
    0 0 1px rgba(0, 0, 0, .98), 0 0 3px rgba(0, 0, 0, .95),
    0 0 6px rgba(0, 0, 0, .88), 0 0 12px rgba(0, 0, 0, .72);
}

.btn-primary.has-liquid > .nx-liquid-label,
.btn-primary.has-liquid > svg {
  position: relative; z-index: 1;
  pointer-events: none;        /* el hover es del metal, no de la etiqueta */
}
.btn-primary.has-liquid > .nx-liquid-label {
  color: #ffffff;
  text-shadow: var(--nx-halo);
}
.btn-primary.has-liquid > svg {
  stroke: #ffffff;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .95)) drop-shadow(0 0 5px rgba(0, 0, 0, .8));
}

/* La tarjeta del login se pone oscura en los dos temas: es donde vive el
   componente y su escenario esta hecho para caer sobre oscuro. */
.login-card:has(.btn-primary.has-liquid),
:root[data-theme='light'] .login-card:has(.btn-primary.has-liquid) {
  background: #0b0c0e; color: #ffffff;
  box-shadow: inset 0 0 0 1px #26272b;
}
.login-card:has(.btn-primary.has-liquid) input {
  background: #131417; color: #ffffff; box-shadow: inset 0 0 0 1px #3a3b40;
}
.login-card:has(.btn-primary.has-liquid) label,
.login-card:has(.btn-primary.has-liquid) .login-sub { color: #b3b3b3; }

/* La accion primaria de cada pantalla lleva el mismo componente. Toma su
   tamano, no al reves, y su halo se desborda por encima de los vecinos sin
   empujarlos: el iframe va absoluto y sin eventos de puntero. */
.filter-btn:not(.secondary) {
  background: transparent; color: var(--paper);
  box-shadow: inset 0 0 0 1px var(--edge); border-radius: var(--r-ui);
}
.filter-btn:not(.secondary):hover { box-shadow: inset 0 0 0 1px var(--paper); }
/* Con metal el hover lo lleva el componente. Sin esto, mi anillo blanco de 1px
   pintaba un recuadro duro encima del filo: el "cuadrado raro". */
.filter-btn.has-liquid:hover, .btn-primary.has-liquid:hover { box-shadow: none; }

/* Mismo trato exacto que la accion primaria: el boton conserva su alto de 32px
   y su relleno horizontal, y el metal se estira a lo que mida. */
.filter-btn.has-liquid {
  position: relative;
  border: 0;
  background: transparent; box-shadow: none;
  overflow: visible;
}
.filter-btn.has-liquid > .nx-liquid {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  border: 0; display: block; z-index: 0;
  pointer-events: auto;
}
.filter-btn.has-liquid > .nx-liquid-label,
.filter-btn.has-liquid > svg {
  position: relative; z-index: 1;
  pointer-events: none;
}
.filter-btn.has-liquid > .nx-liquid-label {
  color: #ffffff;
  text-shadow: var(--nx-halo);
}
.filter-btn.has-liquid > svg {
  stroke: #ffffff;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .95)) drop-shadow(0 0 5px rgba(0, 0, 0, .8));
}
/* La barra necesita alto para el boton nuevo, y los vecinos se alinean con el. */
.filters, .filter-bar, .player-filters { align-items: center; }

/* Y el menu se marca por contraste, sin metal: el componente es un boton
   entero con su etiqueta, no un material que se pueda poner detras de un item
   de navegacion sin reimplementarlo, que es lo que no se debe hacer. */
.nav-item.active {
  background: var(--obsidian); color: var(--paper);
  box-shadow: inset 0 0 0 1px var(--graphite);
}
.nav-item.active svg { stroke: var(--paper); }

/* ==========================================================================
   POR QUE EL MENU NO LLEVA HOLOGRAMA

   Lo intente: un conic-gradient con las ocho paradas de la paleta del shader
   como borde del item activo. Salio un contorno RGB de gamer, no metal.

   Medi el filo de la referencia para entender por que, y estaba equivocado de
   raiz. El filo real tiene DOS tonos, no ocho: azul 217 grados y ambar 20, en
   lados opuestos del borde, y OSCUROS (24 a 36% de luz). Eso es aberracion
   cromatica, dispersion de verdad. Mi degradado barria el espectro entero a
   saturacion plena.

   Y esa es la leccion: el material del componente no se puede fingir en CSS.
   Su filo sale de veinte pasadas de shader sobre una normal animada; un
   degradado estatico solo puede parecerse de lejos, y de cerca se ve barato.
   Los guardrails ya lo decian y yo lo intente igual por otra puerta.

   Asi que el holograma vive UNICAMENTE donde corre el componente autorizado:
   la accion primaria. El menu se marca por contraste, que es lo que hace un
   sistema sin color y lo que ya funcionaba.
   ========================================================================== */

.nav-item.active {
  background: var(--obsidian);
  border: 0;
  box-shadow: inset 0 0 0 1px var(--graphite);
  color: var(--paper);
}
.nav-item.active svg { stroke: var(--paper); }

.tab.active, .atab.active, .nx-viewtab.active {
  background: none;
  border-bottom: 2px solid var(--paper);
  color: var(--paper);
}

*:focus-visible {
  outline: 2px solid var(--paper);
  outline-offset: 2px;
  background-image: none;
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: none; border: 0;
  background: var(--inset);
  box-shadow: inset 0 0 0 2px var(--paper);
}

/* El punto de en vivo vuelve a la menta de Basedash, que es su color de estado. */
.nx-live-dot { background: var(--mint); }
.nx-live-dot::after { background: var(--mint); opacity: .35; }

/* ==========================================================================
   EL FILO, MEDIDO

   Segundo intento, y esta vez con los numeros de la referencia en vez de con
   los mios. Muestreé el contorno del boton original grado a grado:

     tono      barre de 46 a 225 grados, no los 360
     luz       15 a 35 por ciento
     saturacion ~50 por ciento
     contraste  1,02 a 2,11:1 contra su propia superficie

   O sea que la ESTRUCTURA que puse (un barrido conico) era correcta; lo que
   estaba mal era la intensidad. Yo iba a saturacion y luz plenas y sale un
   contorno de gamer; el real es un HILO oscuro que apenas se despega, y por
   eso se lee como dispersion en un canto metalico y no como un adorno.

   Las doce paradas de abajo son el muestreo, no una eleccion. El relleno es el
   mismo casi negro del plate del componente, porque un filo asi solo existe
   contra oscuro. Y gira despacio, que el original tampoco esta quieto.

   Esto NO pretende ser el material: sigue siendo CSS y sigue sin tener sus
   veinte pasadas. Es su mismo principio (aberracion cromatica en el canto) a
   la fidelidad que CSS da de verdad.
   ========================================================================== */

@property --nx-edge-a { syntax: '<angle>'; initial-value: 0deg; inherits: false; }

:root {
  --edge-holo: conic-gradient(from var(--nx-edge-a),
    #453f23 0deg, #2a5935 30deg, #29574c 60deg, #264f4c 90deg,
    #244849 120deg, #234541 150deg, #452f20 180deg, #27532c 210deg,
    #1e384d 240deg, #142439 270deg, #0f1a25 300deg, #271a15 330deg,
    #453f23 360deg);
}

.nav-item.active {
  background:
    linear-gradient(#0b0c0e, #0b0c0e) padding-box,
    var(--edge-holo) border-box;
  border: 1px solid transparent;
  box-shadow: none;
  color: var(--paper);
  animation: nxEdgeSpin 18s linear infinite;
}
.nav-item.active svg { stroke: var(--paper); }

@keyframes nxEdgeSpin { to { --nx-edge-a: 360deg; } }

@media (prefers-reduced-motion: reduce) {
  .nav-item.active { animation: none; }
}

/* ==========================================================================
   LA PASTILLA DEL MENU

   Una sola, que se desliza al item activo. No se remonta al navegar: se mueve
   y se le cambia la etiqueta, asi que el contexto WebGL vive una vez y el
   viaje es lo unico que se anima.

   El item activo se queda transparente por debajo: la pastilla ya dice donde
   estas, y asi el material se ve igual aqui que en la accion primaria, que
   era el otro problema (dos disenos para lo mismo).
   ========================================================================== */

.sidebar-nav {
  position: relative;
  /*
    El escenario del documento es mas ancho que la pastilla (900 unidades de
    sangrado por lado para el bloom), asi que dentro de un menu de 232px
    desbordaba 51 y el menu, que ya scrollea en vertical, se ganaba una barra
    horizontal fantasma. Con clip se corta sin crear contenedor de scroll: el
    halo se queda dentro del panel, que es donde tiene sentido que se quede.
  */
  overflow-x: clip;
}

.nx-nav-pill {
  position: absolute; top: 0; left: 0; z-index: 0;
  padding: 0; border: 0;
  /* mismo cuerpo y mismo radio que el plate del documento: si WebGL no arranca
     queda una pastilla oscura, no un hueco con texto invertido flotando */
  background: #0b0c0e;
  border-radius: 999px;
  color: transparent;                 /* va vacio: el contenido es del item */
  overflow: visible;
  opacity: 0;
  /* el viaje: sale de donde estaba y frena en el destino */
  transition:
    transform .42s cubic-bezier(.22,.68,.32,1),
    width     .42s cubic-bezier(.22,.68,.32,1),
    opacity   .18s ease;
}
.nx-nav-pill > .nx-liquid {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  border: 0; display: block; z-index: 0;
  /*
    El metal SI recibe el puntero, y por eso hay efecto al pasar por encima.
    Con pointer-events:none el componente no se enteraba de que habia un raton:
    ni bloom, ni ondas, ni pulsacion. Nunca. El item de arriba se aparta (ver
    mas abajo) para dejarlo pasar, y el clic vuelve por el mensaje 'activate'
    que el propio puente del documento manda al padre.
  */
  pointer-events: auto;
}

/*
  El item activo se queda con SU icono y SU etiqueta, en su sitio de siempre.

  Esto es lo que arregla las dos quejas a la vez. El componente ya no pinta
  ninguna etiqueta, asi que no hay un segundo texto centrado en la pastilla
  aterrizando encima del icono; y el tratamiento es identico al del boton de
  accion, asi que se acabaron los dos disenos.
*/
html.has-nav-pill .nav-item.active {
  background: transparent;
  box-shadow: none;
  border: 0;
  color: #ffffff;
  text-shadow: var(--nx-halo);  /* legible sobre el metal, se ponga como se ponga */
  position: relative;
  z-index: 1;
  /* Transparente al puntero para que el hover llegue al metal de debajo. El
     clic no lo necesita: ya estas en esta pantalla, y el teclado sigue igual. */
  pointer-events: none;
}
html.has-nav-pill .nav-item.active svg {
  stroke: #ffffff;
  color: #ffffff;
  opacity: 1;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, .95)) drop-shadow(0 0 5px rgba(0, 0, 0, .8));
}
/* Fuera la barra vertical y fuera el filo giratorio de CSS: la pastilla ya
   dice donde estas, y dos marcas para lo mismo es justo el "otro diseno". */
html.has-nav-pill .nav-item.active::before { display: none; }
html.has-nav-pill .nav-item.active { animation: none; }

/* Y el respaldo, que es lo que se ve si no hay WebGL. */
html:not(.has-nav-pill) .nav-item.active {
  background: var(--obsidian); color: var(--paper);
  box-shadow: inset 0 0 0 1px var(--graphite);
}
html:not(.has-nav-pill) .nav-item.active svg { stroke: var(--paper); }

@media (prefers-reduced-motion: reduce) {
  .nx-nav-pill { transition: opacity .18s ease; }
}
