/* Fuentes servidas por nosotros, no por Google: la CSP es default-src 'self'
   y ademas asi el panel abre igual de rapido con mala senial.
   Los .woff2 son los mismos que usa el panel de OpenWRT. */
@font-face {
  font-family: "Outfit";
  src: url("/static/fonts/outfit-variable.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "JetBrains Mono";
  src: url("/static/fonts/jetbrains-mono-variable.woff2") format("woff2");
  font-weight: 100 800;
  font-display: swap;
}

/* Sistema visual. Es la misma familia que la app de clientes (renet-app,
   web/app.css): azul RE NET #2f6ea5 para actuar, Outfit para todo, numeros
   grandes y livianos, y el color de estado reservado para el estado. El panel
   es la version densa: mas filas, radios mas chicos, sin vidrio.
   Tema claro por defecto, oscuro opcional (y el del sistema si es oscuro).
   Todos los colores salen de estas variables: no hay hex sueltos abajo.
   Contrastes medidos (WCAG AA): texto >= 4.5:1 sobre bg, tarjeta y hundido. */

:root {
  /* Le dice al navegador de que color pintar barras de scroll,
     controles nativos y el fondo por defecto. Sin esto los scrolls quedan
     blancos sobre el tema oscuro. */
  color-scheme: light;

  /* Marca. --brand es el FONDO de lo que se aprieta (con texto blanco da
     5.4:1; el #2b7cd3 de antes daba 4.3 en claro y el #5aa0e6 2.8 en oscuro).
     --brand-text es el azul para letras y links sobre el fondo del tema. */
  --navy:        #1b2a5e;
  --brand:       #2f6ea5;
  --brand-dark:  #285e8d;
  --brand-light: #7ab8ee;
  --brand-text:  #1d5b94;
  --on-brand:    #ffffff;
  --accent:      #c42b2b;

  /* Superficies */
  --bg:         #e9edf3;
  --bg-glow:    rgba(47,110,165,0.10);
  --bg-raised:  #ffffff;
  --bg-card:    #ffffff;
  --bg-hover:   rgba(47,110,165,0.07);
  --bg-sunken:  #f1f4f8;

  /* Texto */
  --ink:        #0b1220;
  --ink-muted:  #4a566a;
  --ink-faint:  #5f6a80;
  --ink-invert: #ffffff;

  /* Lineas */
  --line-soft:   rgba(15,23,42,0.10);
  --line-strong: rgba(15,23,42,0.18);

  /* Estados: los de la app, >= 4.5:1 sobre el fondo y sobre su propio tinte */
  --ok:     #0b7a4b;
  --warn:   #8a5200;
  --danger: #c42b2b;
  --ok-bg:     rgba(11,122,75,0.10);
  --warn-bg:   rgba(194,120,0,0.12);
  --danger-bg: rgba(196,43,43,0.09);
  --brand-bg:  rgba(47,110,165,0.11);

  --shadow-sm: 0 1px 2px rgba(15,23,42,0.05);
  --shadow-md: 0 1px 2px rgba(15,23,42,0.05), 0 8px 24px rgba(15,23,42,0.07);
  --shadow-lg: 0 16px 48px rgba(15,23,42,0.18);
  --scrim:     rgba(15,23,42,0.50);

  /* Tipografia: base 16.5px. Outfit para todo, tambien los numeros (con
     cifras tabulares); JetBrains Mono solo para lo que es un identificador:
     IP, MAC, usuario PPPoE, interfaz. */
  --ui:   "Outfit", system-ui, -apple-system, "Segoe UI", sans-serif;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
  /* Subida un escalon entero: el panel se mira muchas horas seguidas y con
     lentes. Nada de texto de 11px, ni siquiera en etiquetas. */
  --fs-xs:  13.5px;
  --fs-sm:  15px;
  --fs-base: 16.5px;
  --fs-md:  18px;
  --fs-lg:  22px;
  --fs-xl:  28px;
  /* Los numeros de las tarjetas: grandes y livianos, como en la app. */
  --fs-num: 30px;

  /* Espaciado en multiplos de 4 */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s8: 32px; --s10: 40px;

  /* Un solo alto para botones, inputs y selects: si cada control elige el
     suyo, una fila con los tres queda escalonada. */
  --ctl-h:    42px;
  --ctl-h-sm: 34px;

  --radius:    12px;
  --radius-sm: 8px;
  --sidebar:   236px;
}

[data-theme="dark"] {
  color-scheme: dark;
  --bg:        #0b0e13;
  --bg-glow:   rgba(47,110,165,0.22);
  --bg-raised: #0f141b;
  --bg-card:   #121820;
  --bg-hover:  rgba(122,184,238,0.08);
  --bg-sunken: #080a0e;

  --ink:       #eef1f5;
  --ink-muted: #a0a9b6;
  --ink-faint: #8b95a4;

  --line-soft:   rgba(255,255,255,0.08);
  --line-strong: rgba(255,255,255,0.15);

  --brand-text:  #7ab8ee;

  --ok:     #3ddc97;
  --warn:   #fbbf24;
  --danger: #ff7a7a;
  --ok-bg:     rgba(61,220,151,0.12);
  --warn-bg:   rgba(251,191,36,0.12);
  --danger-bg: rgba(255,122,122,0.12);
  --brand-bg:  rgba(122,184,238,0.14);

  /* En oscuro no flota nada: bordes, no sombras (como la app). */
  --shadow-sm: none;
  --shadow-md: 0 8px 24px rgba(0,0,0,0.45);
  --shadow-lg: 0 16px 48px rgba(0,0,0,0.6);
  --scrim:     rgba(0,0,0,0.65);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }

/* El brillo azul de arriba es el de la app de clientes: la unica licencia
   decorativa del panel. Fijo, para que no se corra con el scroll. */
body {
  background: radial-gradient(1100px 460px at 60% -12%, var(--bg-glow), transparent 70%) no-repeat fixed, var(--bg);
  color: var(--ink);
  font-family: var(--ui);
  font-size: var(--fs-base);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  font-variant-numeric: tabular-nums;
}

a { color: var(--brand-text); text-decoration: none; }
a:hover { text-decoration: underline; }

:focus-visible {
  outline: 2px solid var(--brand-text);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

/* ---------------------------------------------------------------- layout */

.layout { display: flex; min-height: 100vh; }

.sidebar {
  width: var(--sidebar);
  flex-shrink: 0;
  background: var(--bg-raised);
  border-right: 1px solid var(--line-soft);
  display: flex;
  flex-direction: column;
  position: fixed;
  top: 0; bottom: 0; left: 0;
  overflow-y: auto;
  z-index: 40;
}

.sidebar .brand {
  display: flex; align-items: center; gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--line-soft);
}
.sidebar .brand img { width: 34px; height: 34px; border-radius: var(--radius-sm); }
.sidebar .brand .txt { line-height: 1.2; }
.sidebar .brand .name { font-weight: 600; font-size: var(--fs-md); color: var(--ink); }
.sidebar .brand .node {
  font-family: var(--mono); font-size: var(--fs-xs); color: var(--ink-faint);
}

.nav { padding: var(--s2) var(--s3) var(--s4); flex: 1; }
/* Los cuatro grupos siempre a la vista, cada uno una grilla de dos columnas:
   cualquier pantalla queda a un click (antes eran desplegables). */
.nav .group-titulo {
  font-size: var(--fs-xs); font-weight: 600; color: var(--ink-faint);
  padding: var(--s3) 2px 4px;
}
.nav .group.actual .group-titulo { color: var(--ink-muted); }
.nav .grilla { display: grid; grid-template-columns: 1fr 1fr; gap: 3px; }
.nav .grilla a {
  position: relative; display: flex; align-items: center; min-width: 0;
  padding: 6px var(--s2); margin: 0;
  /* Cada boton una tecla: fondo y borde marcados. Con el borde apenas
     visible el texto parecia flotar en la barra. */
  background: var(--bg-sunken);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  color: var(--ink-muted);
  font-size: var(--fs-sm); font-weight: 500; line-height: 1.25;
  text-decoration: none;
}
.nav .grilla a .nom { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.nav .grilla a:hover { background: var(--bg-hover); border-color: var(--ink-faint); color: var(--ink); text-decoration: none; }
.nav .grilla a.active {
  background: var(--brand-bg); color: var(--brand-text); font-weight: 600;
  border-color: var(--brand);
}
/* El contador va de chapita en la esquina: al lado del nombre no entraba
   ("Conexio… 28") en media columna. */
.nav .grilla a .count {
  position: absolute; top: -6px; right: -3px;
  font-size: 12px; line-height: 16px; font-weight: 600;
  padding: 0 5px; border-radius: 8px;
  background: var(--bg-raised); border: 1px solid var(--line-soft);
  color: var(--ink-muted);
}
.nav .grilla a .count:empty { display: none; }
.nav .grilla a.active .count { color: inherit; }

.sidebar-foot {
  padding: var(--s3) var(--s4);
  border-top: 1px solid var(--line-soft);
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s2);
}
.sidebar-foot .who {
  font-size: var(--fs-xs); color: var(--ink-faint);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

.content { flex: 1; margin-left: var(--sidebar); min-width: 0; }
main { max-width: 1180px; padding: var(--s5) var(--s6) var(--s8); }

.icon-btn {
  background: none; border: 1px solid transparent; cursor: pointer;
  width: var(--ctl-h); height: var(--ctl-h); padding: 0;
  flex-shrink: 0;
  border-radius: var(--radius-sm);
  color: var(--ink-muted); font-size: var(--fs-md); line-height: 1;
}
.icon-btn:hover { background: var(--bg-hover); color: var(--ink); }
.icon-btn .icono { color: inherit; width: 20px; height: 20px; }
/* El de Configuracion del pie es un enlace: que se vea igual que los botones de al lado. */
a.icon-btn { display: inline-flex; align-items: center; justify-content: center;
  color: var(--ink-muted); text-decoration: none; }
a.icon-btn.active { background: var(--bg-hover); color: var(--ink); border-color: var(--line-soft); }
.sidebar-foot .who { margin-right: auto; }

/* Pestañas de Red: las seis pantallas de netmon, arriba del contenido. */
.subnav {
  display: flex; flex-wrap: wrap; gap: 2px; margin: 0 0 var(--s5);
  border-bottom: 1px solid var(--line-soft);
}
.subnav a {
  padding: 6px 12px; font-size: var(--fs-sm); color: var(--ink-muted); text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.subnav a:hover { color: var(--ink); }
.subnav a.active { color: var(--ink); border-bottom-color: var(--brand); font-weight: 600; }
/* Pestañas dentro de una pantalla (Pedidos de la app): mismo aspecto que la subnav, con botones. */
.pestanas-pagina button {
  padding: 6px 12px; font-size: var(--fs-sm); color: var(--ink-muted); background: none; border: 0;
  border-bottom: 2px solid transparent; border-radius: 0; margin-bottom: -1px; cursor: pointer;
}
.pestanas-pagina button:hover { color: var(--ink); }
.pestanas-pagina button.active { color: var(--ink); border-bottom-color: var(--brand); font-weight: 600; }
.pestanas-pagina .cuenta:not(:empty) {
  display: inline-block; min-width: 1.4em; padding: 0 5px; margin-left: 4px; border-radius: 999px;
  background: var(--line-soft); font-size: 12px; text-align: center; font-weight: 600;
}
.filtro-pestana input { min-width: min(100%, 340px); }

/* ------------------------------------------------------------ tipografia */

/* Titulos grandes y livianos, como en la app: la jerarquia la da el tamanio,
   el peso se guarda para los datos. */
h1 { font-size: var(--fs-xl); font-weight: 400; margin: 0 0 var(--s1); letter-spacing: -0.015em; line-height: 1.2; }
h2 {
  font-size: var(--fs-md); font-weight: 600; color: var(--ink);
  margin: var(--s8) 0 var(--s3);
}
h2:first-child { margin-top: 0; }
.sub { color: var(--ink-muted); margin: 0 0 var(--s6); font-size: var(--fs-sm); }
.muted { color: var(--ink-muted); }
.faint { color: var(--ink-faint); }
.mono { font-family: var(--mono); font-size: var(--fs-sm); }
.icono { width: 20px; height: 20px; flex: none; }

/* ---------------------------------------------------------------- cards */

.panel {
  background: var(--bg-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
}
.card { padding: var(--s4); }

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: var(--s3);
}
.metric-tile {
  background: var(--bg-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--s3) var(--s4);
}
.metric-tile .label {
  font-size: var(--fs-sm); text-transform: none; letter-spacing: normal;
  color: var(--ink-muted); margin-bottom: var(--s1); font-weight: 600;
}
.metric-tile .value {
  font-family: var(--ui); font-size: var(--fs-num); font-weight: 300;
  letter-spacing: -0.02em; color: var(--ink); line-height: 1.15;
  display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap;
}
/* El dato de al lado (el %, la cantidad) en su propia capsula: pegado al
   numero grande se leia como si fuera parte del mismo, y no lo es. */
.metric-tile .value small {
  font-family: var(--ui); font-size: var(--fs-xs); font-weight: 500;
  letter-spacing: normal; color: var(--ink-muted); background: var(--bg-sunken);
  border: 1px solid var(--line-soft); border-radius: 999px;
  padding: 2px 8px; line-height: 1.4; white-space: nowrap;
}

/* El color dice de que se trata sin leer la etiqueta: lo cobrado en verde, lo
   que falta en rojo, lo que hay que mirar en amarillo. Solo el numero: una
   tarjeta entera de color es un semaforo, no un tablero. */
.metric-tile.ok .value    { color: var(--ok); }
.metric-tile.ok .value small    { color: var(--ok); background: var(--ok-bg); border-color: transparent; }
.metric-tile.debe .value  { color: var(--danger); }
.metric-tile.debe .value small  { color: var(--danger); background: var(--danger-bg); border-color: transparent; }
.metric-tile.aviso .value { color: var(--warn); }
.metric-tile.aviso .value small { color: var(--warn); background: var(--warn-bg); border-color: transparent; }

/* Una barra finita abajo, con lo cobrado del mes. Un porcentaje se entiende
   mucho mas rapido viendo cuanto falta que leyendo el numero. */
.metric-tile .barra {
  height: 5px; border-radius: 999px; background: var(--bg-sunken);
  margin-top: var(--s2); overflow: hidden;
}
.metric-tile .barra span { display: block; height: 100%; background: var(--ok); }

/* ---------------------------------------------------------------- tablas */

/* Cada tabla scrollea dentro de su propio contenedor: si la pagina entera
   scrollea de costado, las columnas de una tabla terminan encima de otra. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--bg-card);
  box-shadow: var(--shadow-sm);
}

table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
/* Los numeros que cambian (IPs, importes, contadores) no tienen que bailar. */
table .mono, table .num, .metric-tile .value { font-variant-numeric: tabular-nums; }

thead th {
  /* Sin position:sticky. Con dos tablas en la misma pagina los encabezados
     quedan flotando uno sobre otro al scrollear. */
  background: var(--bg-sunken);
  text-align: left;
  font-weight: 600; font-size: var(--fs-sm);
  text-transform: none; letter-spacing: normal;
  color: var(--ink-muted);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line-strong);
  white-space: nowrap;
}
tbody td {
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line-soft);
  vertical-align: middle;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover { background: var(--bg-hover); }
td.num, th.num { text-align: right; }
.val-num { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- badges */

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  padding: 3px 10px; border-radius: 999px;
  font-size: var(--fs-xs); font-weight: 600; line-height: 1.35;
  border: 1px solid transparent; white-space: nowrap;
}
/* Pastillas con tinte y sin borde de color, como las de la app: con el borde
   pleno una tabla de veinte filas era un semaforo y lo grave no resaltaba. */
.val-ok    { background: var(--ok-bg);     color: var(--ok); }
.val-warn  { background: var(--warn-bg);   color: var(--warn); }
.val-crit  { background: var(--danger-bg); color: var(--danger); }
.val-brand { background: var(--brand-bg);  color: var(--brand-text); }
.val-off   { background: var(--bg-sunken); color: var(--ink-faint); border-color: var(--line-soft); }

.dot { display: inline-block; width: 7px; height: 7px; border-radius: 50%; }
.dot.on  { background: var(--ok); }
.dot.off { background: var(--ink-faint); }

/* ---------------------------------------------------------------- forms */

label {
  display: block; font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink); margin-bottom: 6px;
}
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]),
select, textarea {
  width: 100%;
  background: var(--bg-raised); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  font-family: var(--ui); font-size: var(--fs-base);
}
input:not([type=checkbox]):not([type=radio]):not([type=file]):not([type=range]), select {
  height: var(--ctl-h); padding: 0 12px;
}
input[type=file] { font-size: var(--fs-sm); padding: 6px 0; }
select { padding-right: var(--s2); }
textarea {
  padding: 10px 12px;
  font-family: var(--ui); font-size: var(--fs-sm);
  resize: vertical; min-height: 74px;
}
input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-bg);
}
.field { margin-bottom: var(--s4); }
.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--s4); }
.check { display: flex; align-items: flex-start; gap: 10px; margin-bottom: var(--s3); }
.check input { margin-top: 4px; accent-color: var(--brand); flex-shrink: 0; }
.check label { margin: 0; color: var(--ink); font-size: var(--fs-sm); }
.check .hint { display: block; color: var(--ink-faint); font-size: var(--fs-xs); margin-top: 2px; }

button, .btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  height: var(--ctl-h); padding: 0 18px;
  border-radius: var(--radius-sm); cursor: pointer;
  font-family: var(--ui); font-size: var(--fs-sm); font-weight: 600;
  transition-property: background-color, border-color, box-shadow, transform;
  transition-duration: 120ms; transition-timing-function: ease-out;
  line-height: 1; white-space: nowrap;
  border: 1px solid var(--line-strong);
  background: var(--bg-raised); color: var(--ink);
  text-decoration: none;
}
a.btn:hover { text-decoration: none; }
button:hover, .btn:hover { border-color: var(--brand); color: var(--brand-text); }
button.primary, .btn.primary {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand);
}
button.primary .icono, .btn.primary .icono { color: inherit; }
button.primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); color: var(--on-brand); }
button.danger { color: var(--danger); border-color: var(--danger-bg); }
button.danger:hover { background: var(--danger-bg); border-color: var(--danger); color: var(--danger); }
button.small, .btn.small { height: var(--ctl-h-sm); padding: 0 11px; font-size: var(--fs-xs); }
button:disabled { opacity: .45; cursor: not-allowed; }
.actions { display: flex; gap: var(--s2); flex-wrap: wrap; align-items: center; }

/* ---------------------------------------------------------------- modal */

.modal-bg {
  position: fixed; inset: 0; background: var(--scrim);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 56px var(--s5); z-index: 100; overflow-y: auto;
}
.modal {
  background: var(--bg-card); border: 1px solid var(--line-strong);
  border-radius: 14px; width: 100%; max-width: 640px; padding: var(--s6);
  box-shadow: var(--shadow-lg);
}
.modal h3 { margin: 0 0 var(--s4); font-size: var(--fs-lg); font-weight: 600; }
/* Mientras se trae otro rango queda lo anterior, atenuado (sin parpadeo). */
main.cargando .chart-box, main.cargando .tiles { opacity: 0.5; transition: opacity 0.15s; }

/* Atajos del tablero. */
.atajos { display: flex; flex-wrap: wrap; gap: var(--s2); }
.atajo {
  display: inline-flex; align-items: center; gap: var(--s2);
  height: var(--ctl-h); padding: 0 14px; border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--bg-card); color: var(--ink); text-decoration: none; font-weight: 500; font-size: var(--fs-sm);
}
.atajo:hover { border-color: var(--brand); color: var(--brand-text); text-decoration: none; }
.atajo .icono, .btn .icono, button .icono { width: 18px; height: 18px; color: var(--ink-muted); }
.atajo:hover .icono { color: inherit; }
.atajo .count {
  font-size: var(--fs-xs); font-weight: 600; padding: 0 7px; border-radius: 999px;
  background: var(--danger-bg); color: var(--danger);
}
.atajo .count:empty { display: none; }

/* Los que llevan una tabla: en 640px las columnas se aplastan. */
.modal.ancho { max-width: 1100px; }
.modal.ancho td input[type="text"] { min-width: 220px; width: 100%; }
/* Los botoncitos de nodo abajo del casillero: en fila, y si no entran, a la
   linea siguiente; nunca uno por renglon. */
.chips-nodos { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
.chips-nodos button { width: auto; flex: 0 0 auto; }
.modal-foot {
  display: flex; justify-content: flex-end; gap: var(--s2);
  margin-top: var(--s6); align-items: center;
}

pre.code {
  background: var(--bg-sunken); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm); padding: var(--s4);
  font-family: var(--mono); font-size: var(--fs-xs); line-height: 1.6;
  overflow-x: auto; white-space: pre; color: var(--ink);
  max-height: 420px; overflow-y: auto;
}

/* ---------------------------------------------------------------- varios */

.toast {
  position: fixed; bottom: var(--s5); right: var(--s5); z-index: 200;
  padding: var(--s3) var(--s4); border-radius: var(--radius);
  font-size: var(--fs-sm);
  background: var(--bg-raised); border: 1px solid var(--line-strong);
  box-shadow: var(--shadow-md); max-width: 420px;
}
.toast.err { border-color: var(--danger); color: var(--danger); }
.toast.ok  { border-color: var(--ok); color: var(--ok); }

.note-box {
  border-radius: var(--radius); padding: var(--s4) var(--s5);
  font-size: var(--fs-sm); line-height: 1.6;
  border: 1px solid var(--line-soft); background: var(--bg-card);
  color: var(--ink-muted); margin-bottom: var(--s4);
}
.note-box.brand { border-color: var(--brand); background: var(--brand-bg); }
.note-box.amber { border-color: var(--warn); background: var(--warn-bg); }
.note-box strong { color: var(--ink); }

.empty { text-align: center; padding: var(--s10) var(--s5); color: var(--ink-faint); }
.row-between {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--s5); margin-bottom: var(--s5); flex-wrap: wrap;
}
.row-between > div:first-child { flex: 1 1 380px; min-width: 0; }
.spacer { flex: 1; }

.save-bar {
  position: sticky; bottom: 0; margin-top: var(--s5);
  background: var(--bg-raised); border-top: 1px solid var(--line-strong);
  padding: var(--s3) var(--s4); display: flex; align-items: center; gap: var(--s3);
}

/* ------------------------------------------------------------ responsive */

.sidebar-toggle { display: none; }

@media (max-width: 900px) {
  .sidebar {
    transform: translateX(-100%);
    transition: transform .18s ease;
    box-shadow: var(--shadow-lg);
  }
  body.nav-open .sidebar { transform: translateX(0); }
  .content { margin-left: 0; }
  .sidebar-toggle {
    display: inline-flex; align-items: center; gap: var(--s2);
    position: sticky; top: 0; z-index: 30;
    width: 100%; border: none; border-bottom: 1px solid var(--line-soft);
    border-radius: 0; background: var(--bg-raised);
    padding: var(--s3) var(--s4); font-size: var(--fs-md);
  }
  main { padding: var(--s5) var(--s4) var(--s10); }
  .grid2, .grid3 { grid-template-columns: 1fr; }
  /* En el celular lo primero tiene que ser el dato: los atajos del tablero
     ya estan en el menu, y las pestanias van en una fila que se desliza (en
     tres renglones se comian media pantalla). */
  .atajos { display: none; }
  .subnav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .subnav::-webkit-scrollbar { display: none; }
  .subnav a { white-space: nowrap; }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* Identificadores y numeros no se parten: "RN-PMP-LP-N1" en dos lineas se
   lee peor que una columna un poco mas ancha. La fila desplegada queda afuera:
   ahi el primer td es la ficha entera, y un titulo de aviso que no puede
   cortarse estira la tabla y se sale de la pantalla. */
tbody tr:not(.ficha) td:first-child strong,
tbody td .mono,
td.num, .val-num { white-space: nowrap; }

/* La pantalla de login no tiene barra lateral: sin esto el <main> arranca
   pegado al borde izquierdo y la tarjeta queda descentrada en pantallas
   anchas, porque se centra dentro del ancho maximo, no del viewport. */
body > main {
  max-width: none;
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--s5);
}

/* ------------------------------------------------------------- graficos */

.chart-box { position: relative; margin-bottom: var(--s2); }
.chart-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s4); margin-bottom: var(--s2); flex-wrap: wrap;
}
/* Un h2 que se ve como subtitulo: para no saltar de h1 a h3 (el lector de
   pantalla arma el indice con los niveles) sin cambiar como se ve. */
h2.como-h3 { font-size: var(--fs-md); font-weight: 600; margin: 0 0 var(--s3); }
.paso h2.como-h3 { margin-top: 0; }
.chart-head h3, .chart-head h2.como-h3 {
  margin: 0; font-size: var(--fs-sm); font-weight: 600; color: var(--ink);
}
.chart-tip {
  position: absolute; top: 6px; right: 6px;
  background: var(--bg-raised); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); padding: 7px 10px;
  font-size: var(--fs-xs); line-height: 1.5; color: var(--ink);
  box-shadow: var(--shadow-md); pointer-events: none;
  opacity: 0; transition: opacity .1s; white-space: nowrap;
}
.hero-num {
  font-family: var(--ui); font-size: 34px; font-weight: 300; letter-spacing: -0.02em;
  line-height: 1.1; color: var(--ink);
}
.hero-num small { font-size: var(--fs-sm); color: var(--ink-muted); font-family: var(--ui); }
.filtros {
  display: flex; gap: var(--s3); align-items: flex-end;
  flex-wrap: wrap; margin-bottom: var(--s5);
}
.filtros .field { margin-bottom: 0; min-width: 200px; }
.rango { display: flex; gap: 2px; }
.rango button { border-radius: 0; }
.rango button:first-child { border-radius: var(--radius-sm) 0 0 var(--radius-sm); }
.rango button:last-child { border-radius: 0 var(--radius-sm) var(--radius-sm) 0; }

/* ------------------------------------------------- netmon: compatibilidad
   Las vistas de netmon-remote traen sus propias clases. En vez de reescribir
   sus plantillas, se definen acá contra los tokens del panel: heredan tema
   claro y oscuro sin tocar el HTML ni el JS que ya funcionaban. */

:root { --line: var(--line-soft); --bg-overlay: var(--scrim); }

.container { max-width: 100%; padding: 0; }
.card {
  background: var(--bg-card);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--s5);
  margin-bottom: var(--s4);
}
.card > h2, .card > h3 { margin-top: 0; }
.card > h2 {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--s3); margin-bottom: var(--s4);
}

/* Fila de controles sobre los graficos */
.controls {
  display: flex; gap: var(--s3); align-items: center;
  flex-wrap: wrap; margin-bottom: var(--s4);
}
.controls > label { display: flex; align-items: center; gap: var(--s2); margin: 0; }
.controls select, .controls .select { width: auto; min-width: 190px; }
.controls .field { margin-bottom: 0; min-width: 170px; }
.controls label {
  font-size: var(--fs-sm); text-transform: none; letter-spacing: normal;
  color: var(--ink-muted); font-weight: 600;
}

.input, .select, input.input, select.select { /* estilo ya definido por elemento */ }

/* Chips de destinos: seleccion rapida sin llenar la pantalla de casillas */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; margin: 0 6px 6px 0;
  border: 1px solid var(--line-strong); border-radius: 999px;
  background: var(--bg-raised); color: var(--ink-muted);
  font-size: var(--fs-xs); font-family: var(--ui);
  cursor: pointer; user-select: none;
}
.chip:hover { border-color: var(--brand); color: var(--ink); }
.chip.active {
  background: var(--brand-bg); border-color: var(--brand);
  color: var(--brand-text); font-weight: 600;
}

.btn.secondary, button.secondary {
  background: var(--bg-raised); color: var(--ink-muted);
  border-color: var(--line-strong);
}
.btn.secondary:hover, button.secondary:hover { color: var(--ink); border-color: var(--brand); }

/* Matriz de destinos y saltos de traceroute */
.matrix { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.matrix th, .matrix td { padding: var(--s2) var(--s3); border-bottom: 1px solid var(--line-soft); }
.hop-asn { font-family: var(--mono); font-size: var(--fs-xs); color: var(--ink-faint); }
.private { color: var(--ink-faint); font-style: italic; }
.actions-cell { white-space: nowrap; }
.actions-cell { text-align: right; }
.actions-cell button { margin-left: 6px; }
.actions-cell button:first-child { margin-left: 0; }

/* Estos modales se abren agregando la clase .show, no creando el nodo. */
.modal-backdrop { display: none; }
.modal-backdrop.show {
  position: fixed; inset: 0; background: var(--bg-overlay);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 56px var(--s5); z-index: 100; overflow-y: auto;
}
.modal-backdrop .modal, .modal-backdrop > div {
  background: var(--bg-card); border: 1px solid var(--line-strong);
  border-radius: 14px; width: 100%; max-width: 560px; padding: var(--s6);
  box-shadow: var(--shadow-lg); height: fit-content;
}
.modal-actions { display: flex; justify-content: flex-end; gap: var(--s2); margin-top: var(--s5); }

/* El canvas de Chart.js necesita alto propio: si no, colapsa a cero */
canvas { max-width: 100%; }
.chart-wrap { position: relative; height: 300px; }

/* Dos secciones, no un menu largo: la VPN y el monitoreo son dos trabajos
   distintos y casi nunca se usan en la misma sesion. */
.switcher {
  display: flex; gap: 3px; margin: var(--s3) var(--s3) 0;
  padding: 3px; background: var(--bg-sunken);
  border-radius: var(--radius-sm);
}
.switcher a {
  flex: 1; text-align: center; padding: 6px 0;
  border-radius: 5px; font-size: var(--fs-sm); font-weight: 600;
  color: var(--ink-muted); text-decoration: none;
}
.switcher a:hover { color: var(--ink); text-decoration: none; }
.switcher a.active {
  background: var(--bg-raised); color: var(--brand-text);
  box-shadow: var(--shadow-sm);
}

/* ---------------------------------------------------- panel denso (netmon)
   Una celda por destino, todas a la vista. La idea es mirar de reojo y que
   el color y la forma de la miniatura digan si algo anda mal, sin leer. */

.noc-grupo { margin-bottom: var(--s6); }
.noc-grupo > header {
  display: flex; align-items: baseline; gap: var(--s3);
  margin-bottom: var(--s3);
}
.noc-grupo h2 {
  margin: 0; font-size: var(--fs-sm); font-weight: 700;
 
}
.noc-grupo .cuenta { font-size: var(--fs-xs); color: var(--ink-faint); font-family: var(--mono); }

.noc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(178px, 1fr));
  gap: var(--s3);
}

.noc-card {
  background: var(--bg-card);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--ink-faint);
  border-radius: var(--radius-sm);
  padding: var(--s3) var(--s3) var(--s2);
  cursor: pointer;
  transition: border-color .12s, transform .12s;
}
.noc-card:hover { border-color: var(--brand); transform: translateY(-1px); }
.noc-card.ok   { border-left-color: var(--ok); }
.noc-card.warn { border-left-color: var(--warn); }
.noc-card.crit { border-left-color: var(--danger); background: var(--danger-bg); }
.noc-card.mudo { opacity: .55; }

.noc-card .nombre {
  font-size: var(--fs-sm); font-weight: 600; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.noc-card .host {
  font-family: var(--mono); font-size: 12px; color: var(--ink-faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.noc-card .fila {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--s2); margin: var(--s2) 0 4px;
}
.noc-card .valor { font-family: var(--ui); font-size: 22px; font-weight: 400; }
.noc-card .valor small { font-size: 12px; color: var(--ink-muted); font-weight: 400; }
.noc-card .estado { font-size: 15px; line-height: 1; }
.noc-card.ok   .estado { color: var(--ok); }
.noc-card.warn .estado { color: var(--warn); }
.noc-card.crit .estado, .noc-card.crit .valor { color: var(--danger); }
.noc-card .spark { display: block; width: 100%; height: 26px; }
.noc-card .pie {
  display: flex; justify-content: space-between;
  font-size: 12px; font-family: var(--mono); color: var(--ink-faint);
  margin-top: 2px;
}

.noc-barra {
  display: flex; gap: var(--s5); flex-wrap: wrap; align-items: center;
  padding: var(--s3) var(--s4); margin-bottom: var(--s5);
  background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius);
}
.noc-barra .kpi { display: flex; align-items: baseline; gap: 7px; }
.noc-barra .kpi b { font-family: var(--ui); font-size: var(--fs-lg); font-weight: 400; }
.noc-barra .kpi span { font-size: var(--fs-xs); color: var(--ink-faint);
  }
.noc-barra .sep { flex: 1; }


/* ------------------------------------------------- selector de color (grupos)
   El campo hex solo no dice nada: al lado va la muestra real y una fila de
   colores ya elegidos, que es lo que se usa el 90% de las veces. */
.color-field { display: flex; align-items: center; gap: var(--s2); }
.color-field input[type=color] {
  width: var(--ctl-h); height: var(--ctl-h); padding: 2px;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--bg-raised); cursor: pointer; flex-shrink: 0;
}
.color-field input[type=text] { flex: 1; font-family: var(--mono); }
.color-presets { display: flex; gap: 5px; margin-top: var(--s2); flex-wrap: wrap; }
.color-presets button {
  width: 22px; height: 22px; padding: 0; min-width: 0;
  border-radius: 50%; border: 1px solid var(--line-strong);
}
.color-presets button:hover { transform: scale(1.15); }

/* Un punto del color del grupo delante del nombre: mas legible que teñir
   el texto, que en claro pierde contraste. */
.gdot {
  display: inline-block; width: 9px; height: 9px; border-radius: 50%;
  flex-shrink: 0; border: 1px solid rgba(0,0,0,.15);
}

/* Tipo de destino: gateway o CPE */
.kind {
  display: inline-block; padding: 1px 7px; border-radius: 4px;
  font-size: 12px; font-weight: 700; letter-spacing: .05em;
  font-family: var(--mono); text-transform: uppercase;
  border: 1px solid var(--line-strong); color: var(--ink-faint);
}
.kind.gw  { color: var(--brand-text); border-color: var(--brand); background: var(--brand-bg); }
.kind.cpe { color: var(--ink-muted); background: var(--bg-sunken); }

/* -------------------------------------------- resumen por grupo (panel NOC) */
.noc-grupo > header {
  flex-wrap: wrap; align-items: center;
  padding-bottom: var(--s2); border-bottom: 1px solid var(--line-soft);
}
.noc-grupo h2 { display: flex; align-items: center; gap: var(--s2); }
.noc-resumen {
  display: flex; gap: var(--s4); flex-wrap: wrap;
  margin-left: auto; align-items: baseline;
}
.noc-resumen .m { display: flex; align-items: baseline; gap: 5px; }
.noc-resumen .m span {
  font-size: 12px;
  color: var(--ink-faint); font-weight: 600;
}
.noc-resumen .m b {
  font-family: var(--ui); font-size: var(--fs-sm); font-weight: 600; color: var(--ink);
}
.noc-resumen .m b.ok   { color: var(--ok); }
.noc-resumen .m b.warn { color: var(--warn); }
.noc-resumen .m b.crit { color: var(--danger); }

/* Detalle extra de la tarjeta: min / prom / max de la ventana */
.noc-card .detalle {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: 2px; margin-top: var(--s2);
  padding-top: var(--s2); border-top: 1px solid var(--line-soft);
  font-family: var(--mono); font-size: 12px; color: var(--ink-faint);
}
.noc-card .detalle div { display: flex; flex-direction: column; line-height: 1.35; }
.noc-card .detalle u {
  text-decoration: none; font-size: 12px;
  font-family: var(--ui); font-weight: 600;
}
.noc-card .detalle i { font-style: normal; color: var(--ink-muted); }
.noc-card .cab { display: flex; align-items: center; gap: 5px; justify-content: space-between; }
.noc-card .cab .nombre { flex: 1; min-width: 0; }

/* Error dentro de un modal: sin alto reservado el dialogo salta cuando
   aparece el mensaje. */
.modal-error {
  color: var(--danger); font-size: var(--fs-xs);
  min-height: 18px; margin-top: var(--s2);
}


/* renet-fact: variante roja del aviso, para errores de formulario. */
.note-box.rojo { border-color: var(--danger); background: var(--danger-bg); }

/* --- renet-fact: lo que en renet-vpn iba con style= inline ---
   Aca el CSP es style-src 'self' sin unsafe-inline, asi que un atributo style
   no se aplica: el navegador lo bloquea y la pantalla queda a medio armar. */
.login { max-width: 380px; margin: 72px auto; padding: 0 20px; }
.login-cabecera { text-align: center; margin-bottom: 28px; }
.login-cabecera img { width: 72px; height: 72px; border-radius: 12px; }
.login-cabecera h1 { margin: 14px 0 2px; }
.login-cabecera .sub { margin: 0; }
.login-error { margin-bottom: 16px; }
.ancho-total { width: 100%; }
.linea { display: inline; }
.separado { margin: 16px 0; }
.nota-pie { margin-top: 6px; }

/* El switcher en renet-vpn vive en el sidebar y ocupa todo el ancho. Aca va en
   el contenido, asi que se le pone un techo o queda un boton gigante. */
.switcher.separado { max-width: 320px; }

/* Los controles de una barra se alinean por abajo, no por el centro.
   Un .field trae la etiqueta arriba del input, asi que centrarlo contra un
   boton pelado deja el boton flotando a media altura: es lo que se ve como
   "botones de tamanios distintos" aunque midan lo mismo. */
.controls { align-items: flex-end; }
.controls .check { margin-bottom: 0; align-items: center; height: var(--ctl-h); }
/* En una barra de controles las etiquetas van en versalitas, pero la de un
   checkbox es texto que se lee, no el nombre de un campo. */
.controls .check label {
  text-transform: none; letter-spacing: normal;
  font-weight: 400; font-size: var(--fs-sm); color: var(--ink);
}
.controls .conteo { margin-left: auto; font-size: var(--fs-sm); }
.controls .field.ancho { min-width: 280px; flex: 0 1 320px; }

/* Las clases val-* son estilos de badge: traen fondo y borde. Sobre el numero
   grande de un tile eso se ve como una barra de color pegada al numero; ahi
   alcanza con teñir el texto. */
.metric-tile .value.val-ok,
.metric-tile .value.val-warn,
.metric-tile .value.val-crit {
  background: none; border: 0; padding: 0; color: var(--danger);
}

/* --- ficha expandible de un cliente --- */
tr.cabecera { cursor: pointer; }
tr.cabecera:hover { background: var(--bg-hover); }
td.expandir, th.expandir { width: 46px; white-space: nowrap; }
.flecha { display: inline-block; width: 12px; color: var(--ink-faint); }
/* La flecha es un boton para poder abrir la ficha con el teclado; el click en
   cualquier lugar de la fila sigue andando (lo toma el tr). */
button.abrir-ficha {
  height: 28px; width: 28px; padding: 0; margin-right: 2px;
  border: 1px solid transparent; background: none; vertical-align: middle;
}
button.abrir-ficha:hover { border-color: var(--line-strong); }
.estado-normal { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-muted); font-size: var(--fs-sm); }
.modal:focus { outline: none; }

tr.ficha > td { background: var(--bg-sunken); padding: var(--s4) var(--s5); }
/* Cuatro bloques (planilla, UISP, trafico, MikroTik) que se acomodan segun el
   ancho: fijar tres columnas dejaba el cuarto solo en una fila. */
.fuentes { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: var(--s4); }
@media (max-width: 700px) { .fuentes { grid-template-columns: 1fr; } }

.fuente {
  background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s3) var(--s4);
}
.fuente h3 {
  margin: 0 0 var(--s3); font-size: var(--fs-md); font-weight: 600;
  letter-spacing: normal; color: var(--ink); text-wrap: balance;
}
.par { display: flex; justify-content: space-between; gap: var(--s4); padding: 5px 0; font-size: var(--fs-sm); }
.par .k { color: var(--ink-muted); flex-shrink: 0; }
.par .v { text-align: right; word-break: break-word; }
.equipos { margin-top: var(--s2); padding-top: var(--s2); border-top: 1px solid var(--line-soft); }
.problemas { margin-bottom: var(--s4); display: flex; flex-direction: column; gap: var(--s2); }
.problemas .note-box { margin: 0; }

/* --- confirmacion de un cambio en la planilla --- */
.cambio { border-top: 1px solid var(--line-soft); padding: var(--s3) 0; }
.cambio:first-child { border-top: 0; }
.cambio-cab { display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3); margin-bottom: 6px; }
.cambio-cab label { margin: 0; color: var(--ink); font-weight: 500; }
.cambio-cab .faint { font-size: var(--fs-xs); }
.cambio-valores { display: flex; align-items: flex-end; gap: var(--s3); }
.cambio-valores .antes, .cambio-valores .despues { flex: 1; min-width: 0; }
.cambio-valores .k {
  display: block; font-size: 12px;
  letter-spacing: .07em; color: var(--ink-faint); margin-bottom: 3px;
}
.cambio-valores .antes .mono { display: block; padding: 8px 0; color: var(--ink-muted); }
.cambio-valores input { width: 100%; }
.flecha-cambio { color: var(--ink-faint); padding-bottom: 10px; }

/* Cuando el overlay muestra mas de un cliente (los duplicados), cada uno lleva
   su encabezado para no mezclar los campos de uno con los del otro. */
.cliente-overlay {
  margin: var(--s4) 0 var(--s2); padding-top: var(--s3);
  border-top: 1px solid var(--line-strong);
  font-size: var(--fs-sm); font-weight: 600;
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
}
.cliente-overlay:first-child { margin-top: 0; padding-top: 0; border-top: 0; }
.cliente-overlay .faint { font-weight: 400; font-size: var(--fs-xs); }

/* --- trafico: lista a la izquierda, graficos a la derecha --- */
.trafico-cols { display: grid; grid-template-columns: 280px 1fr; gap: var(--s5); align-items: start; }
@media (max-width: 900px) { .trafico-cols { grid-template-columns: 1fr; } }
.lista-trafico { max-height: 70vh; overflow-y: auto; }
.lista-trafico td { padding: 0; }
.enlace-fila {
  display: flex; flex-direction: column; align-items: flex-start; gap: 2px;
  width: 100%; height: auto; padding: 10px var(--s3); border: 0;
  background: none; text-align: left; border-radius: 0;
}
.enlace-fila:hover { background: var(--bg-hover); border-color: transparent; color: var(--ink); }
.enlace-fila .faint { font-size: var(--fs-xs); }
.grafico img {
  width: 100%; max-width: 100%; height: auto;
  border-radius: var(--radius-sm); background: #fff; margin: var(--s2) 0;
}
.grafico h3 { margin: 0; font-size: var(--fs-sm); font-weight: 600; }
.grafico h3 .faint { font-weight: 400; font-size: var(--fs-xs); }
.tabla-trafico { border-top: 1px solid var(--line-soft); padding-top: var(--s2); }

/* --- trafico dentro de la ficha del cliente --- */
.trafico-ficha .graficos { margin-top: var(--s3); display: grid; gap: var(--s2); }
.trafico-ficha figure { margin: 0; }
.trafico-ficha img {
  width: 100%; height: auto; border-radius: var(--radius-sm);
  background: #fff; display: block;
}
.trafico-ficha figcaption { font-size: var(--fs-xs); color: var(--ink-muted); margin-top: 2px; }

/* --- elegir entre varias correcciones --- */
.opciones { display: flex; flex-direction: column; gap: var(--s2); }
.opcion {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  width: 100%; height: auto; padding: var(--s3); text-align: left;
  border: 1px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--bg-raised); white-space: normal;
}
.opcion:hover { border-color: var(--brand); background: var(--bg-hover); }
.opcion-titulo { font-weight: 600; color: var(--ink); }
.opcion .faint { font-size: var(--fs-sm); }
.opcion-que { font-size: var(--fs-xs); color: var(--brand-text); }

/* ------------------------------------------------ uplinks (netmon) */
/* Lo dibuja cuadro-uplinks.js. Paleta de origenes de netmon: 8 tonos
   validados para daltonismo. */
.uc-c0 { --uc: #8792a6; }
.uc-c1 { --uc: #2a78d6; }  .uc-c2 { --uc: #eb6834; }
.uc-c3 { --uc: #1baf7a; }  .uc-c4 { --uc: #eda100; }
.uc-c5 { --uc: #e87ba4; }  .uc-c6 { --uc: #008300; }
.uc-c7 { --uc: #4a3aa7; }  .uc-c8 { --uc: #e34948; }

.uc-ok   { color: var(--ok); }
.uc-warn { color: var(--warn); }
.uc-crit { color: var(--danger); }
.uc-nada { color: var(--ink-faint); }

.uc-cuadro {
  background: var(--bg-card); color: var(--ink);
  border: 1px solid var(--line-soft); border-left: 3px solid var(--ok);
  border-radius: 10px; padding: 12px 14px; max-width: 420px;
  font-family: var(--ui);
}
.uc-borde-uc-warn { border-left-color: var(--warn); }
.uc-borde-uc-crit { border-left-color: var(--danger); }
.uc-cab { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; gap: 8px; }
.uc-titulo { font-size: 12px; font-weight: 700; }
.uc-sub { font-size: 12px; opacity: .6; white-space: nowrap; }
.uc-fila {
  display: grid; grid-template-columns: 10px 1fr auto 16px 120px; gap: 8px;
  align-items: center; padding: 5px 0; border-top: 1px solid var(--line-soft);
}
.uc-muestra { width: 10px; height: 10px; border-radius: 3px; background: var(--uc); }
.uc-nombre { font-weight: 600; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.uc-lat { font-family: var(--mono); font-size: 13px; text-align: right; white-space: nowrap; }
.uc-lat small { opacity: .65; }
.uc-icono { font-weight: 700; text-align: center; }
.uc-mini { display: block; }
.uc-linea { fill: none; stroke: var(--uc); stroke-width: 1.6; stroke-linejoin: round; stroke-linecap: round; }
.uc-perdida { fill: var(--danger); }
.uc-pie { display: flex; justify-content: space-between; margin-top: 6px; font-size: 12px; opacity: .7; }
.uc-pie a { color: inherit; }
.uc-vacio { padding: 8px 0; opacity: .6; }
.uc-viejo { font-size: 12px; color: var(--warn); padding: 2px 0 6px; }

/* Version de la barra lateral: sin miniatura, entra en el ancho del menu. */
.uc-compacto { padding: 8px 10px; max-width: none; border-radius: var(--radius-sm); }
.uc-compacto .uc-fila { grid-template-columns: 8px 1fr auto 12px; gap: 6px; padding: 3px 0; }
.uc-compacto .uc-muestra { width: 8px; height: 8px; }
.uc-compacto .uc-nombre, .uc-compacto .uc-lat { font-size: 12px; }
.uc-compacto .uc-titulo { font-size: 12px; }

/* Estado: uplinks a la izquierda, trafico de las VLANs a la derecha. */
/* Ocupa todo el ancho, igual que la fila de metricas de abajo. Los graficos
   marcan la altura; el cuadro de netmon se estira y reparte sus filas para
   terminar parejo con ellos. */
.estado-red {
  display: grid; grid-template-columns: minmax(320px, 2fr) minmax(0, 3fr);
  gap: var(--s4); align-items: stretch; margin-bottom: var(--s5);
}
.uplinks-tablero .uc-cuadro {
  height: 100%; max-width: none; box-sizing: border-box;
  display: flex; flex-direction: column; justify-content: space-between;
}
/* Con mas ancho, la miniatura crece: se lee mejor la forma de la curva. */
.uplinks-tablero .uc-fila { padding: 8px 0; grid-template-columns: 10px 1fr auto 16px minmax(120px, 45%); }
.uplinks-tablero .uc-mini { width: 100%; }
.uplinks-tablero:empty { display: none; }
.graficos-red { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s2); }
.grafico-red {
  margin: 0; background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: 10px; padding: 6px 8px;
}
.grafico-red figcaption {
  font-size: 12px; font-weight: 600; margin-bottom: 3px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.grafico-red figcaption .faint { font-weight: 400; font-size: 12px; }
/* Los Mbps de ahora, chicos y a la derecha del titulo: el que se corta con
   "…" es el nombre, no el numero. */
.grafico-red figcaption { display: flex; align-items: baseline; gap: 6px; }
.grafico-red figcaption .faint {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; flex: 1;
}
.grafico-red .mbps-ahora {
  margin-left: auto; flex-shrink: 0;
  font-size: 12px; font-weight: 500; color: var(--ink-muted);
}
.grafico-red img { display: block; width: 100%; height: auto; cursor: zoom-in; border-radius: 4px; }
.grafico-red.sin-grafico img { display: none; }
.grafico-red.sin-grafico::after {
  content: "El router no devolvió este gráfico."; display: block;
  font-size: 12px; color: var(--ink-faint); padding: var(--s3) 0;
}
@media (max-width: 1100px) {
  .estado-red { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
  .graficos-red { grid-template-columns: 1fr; }
}

/* ------------------------------------------------ clientes y conexiones */
.botonera { display: flex; gap: var(--s2); flex-wrap: wrap; }
.modal-backdrop .modal.modal-ancho { max-width: 760px; }
/* Los botoncitos de arriba del modal (✓ guardar, ✕ cerrar). Alto cero para
   no empujar el titulo; sticky para que sigan a la vista al scrollear. */
.modal-barra {
  position: sticky; top: 10px; height: 0; z-index: 3;
  display: flex; justify-content: flex-end; gap: 4px;
}
.modal-barra button {
  width: 26px; height: 26px; min-height: 0; padding: 0; line-height: 1;
  font-size: 13px; border-radius: 6px; transform: translate(10px, -14px);
  box-shadow: var(--shadow-md);
}
.modal-barra .modal-barra-ok { background: var(--brand); border-color: var(--brand); color: var(--on-brand); }
.modal-barra .modal-barra-ok:hover { background: var(--brand-dark); }
.modal-barra .modal-barra-ok[disabled] { opacity: .45; cursor: default; }
/* QR WiFi: grande y con fondo blanco siempre, que la camara lo lea aun en tema oscuro. */
.modal-backdrop .modal.modal-wifi { max-width: 420px; text-align: center; }
.wifi-qr { display: block; width: min(300px, 100%); height: auto; margin: var(--s3) auto var(--s4);
  background: #fff; border-radius: 8px; image-rendering: pixelated; }
.wifi-dato { display: flex; align-items: center; gap: var(--s3); padding: var(--s2) 0;
  border-top: 1px solid var(--line-soft); text-align: left; }
.wifi-dato .k { min-width: 3.5em; color: var(--ink-faint); font-size: var(--fs-sm); }
.wifi-dato .v { flex: 1; font-size: var(--fs-md); word-break: break-all; user-select: all; }
.wifi-ayuda { margin-top: var(--s3); font-size: var(--fs-xs); text-align: left; }

/* ---------------------------------------------------------------- taller */
.taller-form fieldset { border: 0; padding: 0; margin: var(--s4) 0 0; min-width: 0; }
.fila-boton { display: flex; gap: var(--s2); align-items: center; }
.fila-boton input { flex: 1; min-width: 0; }
.form-grid .checks { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); }
.ptp-lados { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s5); }
.ptp-lados h3 { margin: var(--s3) 0; }
.trabajo { margin-bottom: var(--s3); }
.trabajo pre.code { margin-top: var(--s3); }
.trabajo .botonera { flex-shrink: 0; }
.texto-error { color: var(--danger); margin-top: var(--s1); }
.fila-boton input.angosto { flex: 0 0 64px; color: var(--ink-faint); }

/* Lista de coincidencias mientras se escribe la conexion (Taller). */
.autocompletar { position: relative; }
.lista-sugerencias {
  position: absolute; top: 100%; left: 0; right: 0; z-index: 20;
  margin: 2px 0 0; padding: 4px 0; list-style: none; max-height: 260px; overflow-y: auto;
  background: var(--bg-card); border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-md);
}
.lista-sugerencias li {
  display: flex; justify-content: space-between; gap: var(--s3);
  padding: 6px 10px; font-size: var(--fs-sm); cursor: pointer;
}
.lista-sugerencias li .faint { font-family: var(--mono); font-size: var(--fs-xs); }
.lista-sugerencias li:hover, .lista-sugerencias li.activo { background: var(--bg-hover); }
.lista-sugerencias li.sin-match { cursor: default; }
@media (max-width: 760px) {
  .ptp-lados { grid-template-columns: 1fr; }
  .trabajo .row-between { flex-wrap: wrap; }
}
.form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 var(--s5); }
.form-grid .field { margin-bottom: var(--s4); }
.form-grid .span2, .form-grid .span3 { grid-column: 1 / -1; }
.form-grid .field > .faint { display: block; margin-top: 4px; font-size: var(--fs-xs); }
@media (max-width: 760px) {
  .form-grid { grid-template-columns: 1fr; }
}
.form-grid textarea { width: 100%; resize: vertical; }
.sugerencias { margin-top: 6px; font-size: var(--fs-xs); }
.sugerencias .chip { margin: 4px 4px 0 0; font-size: var(--fs-xs); padding: 2px 9px; }
.vincular { display: flex; gap: var(--s2); align-items: center; margin: var(--s2) 0 var(--s4); }
.vincular input { flex: 1; }
.vincular select { width: auto; }
.lista-conexiones { border: 1px solid var(--line-soft); border-radius: var(--radius-sm); padding: 4px var(--s3); }
.modal-actions .espacio { flex: 1; }
button.peligro { color: var(--danger); border-color: var(--danger); background: none; }

/* Conexiones: el titular debajo del nombre de la fila, y el bloque de datos
   de la conexion arriba de las cuatro fuentes. */
.titulares { font-size: var(--fs-xs); margin-top: 2px; }
.detalle-conexion {
  background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s3) var(--s4); margin-bottom: var(--s4);
}
.detalle-conexion h3 { margin: 0; font-size: var(--fs-md); font-weight: 600; color: var(--ink); }
.detalle-conexion .row-between { margin-bottom: var(--s2); align-items: center; }
/* Cada dato es un bloque: etiqueta arriba, valor abajo, todo alineado a la
   izquierda. Antes la etiqueta quedaba pegada a un borde y el valor al otro,
   y con tres columnas nada coincidia con nada. */
.detalle-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--s3) var(--s5); align-items: start;
}
.detalle-conexion .par { display: block; padding: 0; }
.detalle-conexion .par .k {
  display: block; font-size: var(--fs-xs); color: var(--ink-muted); margin-bottom: 2px;
}
/* El valor puede ser texto, o texto con botones al lado (Ver, Copiar, QR). En
   linea quedaban a distinta altura y los botones se caian a un renglon aparte:
   con flex quedan todos sobre la misma linea y el alto de cada fila parejo. */
.detalle-conexion .par .v {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  min-height: var(--ctl-h-sm); text-align: left; font-size: var(--fs-sm); overflow-wrap: anywhere;
}
/* Tres columnas por tema: quien es el cliente, que equipos tiene y como esta el
   servicio. Cada dato queda debajo del de su tema y no flotando al lado de
   cualquier otro, que es lo que hacia ilegible la ficha. */
.detalle-cols {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: var(--s4) var(--s6); align-items: start;
}
.detalle-col h4 {
  margin: 0 0 var(--s2); padding-bottom: 4px; border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: .06em; color: var(--ink-muted);
}
.detalle-col .par + .par { margin-top: var(--s2); }
/* Las dos partes del modal de edicion: lo que va a la planilla y lo que queda en el panel. */
.titulo-bloque {
  margin: var(--s4) 0 var(--s3); padding-bottom: 4px; border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-sm); font-weight: 600;
}
.titulo-bloque .faint { font-weight: 400; font-size: var(--fs-xs); }
.detalle-col .v input[type="text"] { flex: 1 1 140px; min-width: 0; }
.detalle-col .v select { flex-shrink: 0; }
.origen {
  font-size: 12px; color: var(--ink-faint);
  border: 1px solid var(--line-soft); border-radius: 4px; padding: 0 4px; margin-left: 4px;
}
.secreto { letter-spacing: .05em; margin-right: 4px; }

/* Configuracion: el resultado de "Probar" al lado de cada fuente. */
.prueba { font-size: var(--fs-sm); min-height: 18px; margin-bottom: var(--s2); }
.prueba.ok-texto { color: var(--ok); }
.prueba.val-crit { color: var(--danger); background: none; border: 0; padding: 0; }

/* Alta paso a paso: la barra de pasos y las dos opciones grandes. */
.pasos {
  display: flex; gap: var(--s2); list-style: none; padding: 0; margin: 0 0 var(--s4);
  counter-reset: paso; flex-wrap: wrap;
}
.pasos li {
  counter-increment: paso; flex: 1; min-width: 130px;
  display: flex; align-items: center; gap: var(--s2);
  padding: var(--s3) var(--s4); border-radius: var(--radius-sm);
  border: 1px solid var(--line-soft); color: var(--ink-muted); font-size: var(--fs-sm);
}
.pasos li::before {
  content: counter(paso);
  display: grid; place-items: center; flex-shrink: 0;
  width: 26px; height: 26px; border-radius: 50%;
  background: var(--bg-sunken); color: var(--ink-muted);
  font-weight: 700; font-size: var(--fs-xs);
}
.pasos li.actual {
  border-color: var(--brand); color: var(--ink); font-weight: 600;
  background: var(--brand-bg); box-shadow: var(--shadow-sm);
}
.pasos li.actual::before { background: var(--brand); color: var(--on-brand); }
.pasos li.hecho { color: var(--ok); border-color: var(--ok-bg); }
.pasos li.hecho::before { content: "✓"; background: var(--ok-bg); color: var(--ok); }
.paso h3 { margin-top: 0; }

/* Taller en la casa: lo que sigue el tecnico en el celular, en grande. */
.casa-pasos li { min-width: 0; padding: var(--s2) var(--s3); }
.casa-etapa { border-left: 6px solid var(--brand); margin-bottom: var(--s4); }
.casa-etapa h2 { margin: 0 0 var(--s2); font-size: var(--fs-xl); line-height: 1.2; }
.casa-etapa p { font-size: var(--fs-md); margin: 0 0 var(--s2); }
.casa-etapa.espera { border-left-color: var(--brand); }
.casa-etapa.accion { border-left-color: var(--warn); background: var(--warn-bg); }
.casa-etapa.ok { border-left-color: var(--ok); background: var(--ok-bg); }
.casa-etapa.mal { border-left-color: var(--danger); background: var(--danger-bg); }
.casa-etapa.apagado { border-left-color: var(--line-strong); }
.casa-registro { margin-top: var(--s4); }
@media (max-width: 640px) {
  .casa-pasos li { flex-basis: calc(50% - var(--s2)); }
}
.opciones-grandes { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); margin-bottom: var(--s4); }
@media (max-width: 640px) { .opciones-grandes { grid-template-columns: 1fr; } }
.arca-fila { display: flex; align-items: center; gap: var(--s2); }

/* El atributo hidden pierde contra cualquier regla que fije display (una tabla,
   un flex). Sin esto, ocultar algo con el = true no hace nada y se ve un panel
   a medio armar. */
[hidden] { display: none !important; }

/* --- grafico agrandado --- */
.lupa { display: none; }
.lupa.show {
  position: fixed; inset: 0; z-index: 200;
  background: rgba(6,9,16,0.86);
  display: flex; align-items: center; justify-content: center;
  padding: var(--s5); cursor: zoom-out;
}
.lupa img {
  /* El router sirve 500x170; el doble es lo maximo antes de que se note
     pixelado. crisp-edges mantiene finas las lineas del grafico. */
  width: min(1000px, 96vw); height: auto; max-height: 92vh;
  image-rendering: crisp-edges;
  background: #fff; border-radius: var(--radius); box-shadow: var(--shadow-lg);
}
.trafico-ficha img { cursor: zoom-in; }

/* Aviso de que hay escrituras en curso, mientras se sigue trabajando. */
.encurso {
  position: fixed; left: var(--s5); bottom: var(--s5); z-index: 150;
  background: var(--bg-card); color: var(--ink-muted);
  border: 1px solid var(--line-strong); border-radius: 999px;
  padding: 8px 16px; font-size: var(--fs-sm); box-shadow: var(--shadow-md);
}

/* Un valor largo no tiene que desbordar la tarjeta: la etiqueta y el valor
   pueden caer en renglones distintos, y la bajada y la subida tambien. */
.par { flex-wrap: wrap; }
.par .v { min-width: 0; overflow-wrap: anywhere; }
.trafico-valor { display: flex; gap: var(--s3); flex-wrap: wrap; justify-content: flex-end; }

/* Cada equipo del cliente es un bloquecito propio: con senial, rate, airtime y
   su AP, una lista plana se vuelve ilegible. */
.equipo { padding: var(--s2) 0; border-top: 1px dashed var(--line-soft); }
.equipo:first-child { border-top: 0; }
.equipo .par { gap: var(--s2); }
.equipo .v { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }

/* El grafico de latencia va dentro de una columna angosta: sin alto propio el
   SVG colapsa. */
.latencia-ficha .chart-box { margin-top: var(--s3); min-height: 120px; }
.latencia-ficha .chart-box svg { width: 100%; height: auto; }
.nota-escala { font-size: var(--fs-xs); margin: 4px 0 0; }

/* Controles del grafico de latencia, en una columna angosta. */
.sub-titulo {
  margin: var(--s3) 0 var(--s2); padding-top: var(--s2);
  border-top: 1px solid var(--line-soft);
  font-size: 12px;
  color: var(--ink-faint); font-weight: 600;
}
.sub-titulo .faint { text-transform: none; letter-spacing: normal; font-weight: 400; }
.controles-grafico {
  display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
  margin: var(--s2) 0; align-items: center;
}
.controles-grafico label {
  display: flex; align-items: center; gap: 6px; margin: 0;
  font-size: var(--fs-xs); color: var(--ink-muted);
}
.controles-grafico select { height: var(--ctl-h-sm); padding: 0 6px; font-size: var(--fs-xs); width: auto; min-width: 0; }
.controles-grafico input[type="range"] { width: 84px; accent-color: var(--brand); }
.valor-suavizado { min-width: 14px; text-align: right; }


/* --------------------------------------------------- detalles que faltaban */
/* .solo-persona se muestra u oculta con [hidden]; no necesita estilo propio,
   pero si estar declarada para que se vea que es intencional. */
.solo-persona { display: block; }

.importar-carga { margin-bottom: var(--s5); }
.importar-carga .note-box { margin: var(--s3) 0 0; }

/* La linea del chat de donde salio un dato: se lee, pero no compite. */
.evidencia {
  font-size: var(--fs-xs); line-height: 1.45; max-width: 46ch;
  overflow-wrap: anywhere; text-wrap: pretty;
}

/* Titulos y textos cortos: sin una palabra huerfana en la ultima linea. */
h1, h2, h3, .opcion-titulo { text-wrap: balance; }
.sub, .faint, .note-box, figcaption { text-wrap: pretty; }

/* Los graficos del router son imagenes claras sobre fondo claro. */
.grafico-red img, .graficos img {
  outline: 1px solid rgba(0, 0, 0, .10); outline-offset: -1px;
}
[data-theme="dark"] .grafico-red img, [data-theme="dark"] .graficos img {
  outline-color: rgba(255, 255, 255, .12);
}

/* Un boton chico sigue teniendo que poder apretarse. */
button.small, .btn.small { min-height: var(--ctl-h-sm); padding: 0 12px; font-size: var(--fs-sm); }
button:active:not(:disabled), .btn:active { transform: scale(.98); }
@media (prefers-reduced-motion: reduce) {
  button, .btn { transition-duration: 0ms; }
  button:active:not(:disabled), .btn:active { transform: none; }
}

/* Un campo que ocupa toda la fila para no descolocar los pares de al lado,
   pero cuyo control no necesita ser ancho (un select de dos opciones). */
.field.angosto select, .field.angosto input { max-width: 340px; }

/* Las dos opciones grandes del alta: se eligen de un vistazo, no se leen. */
.opciones-grandes .opcion {
  display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
  height: auto; min-height: 96px; padding: var(--s4) var(--s5); text-align: left;
  white-space: normal; line-height: 1.45;
}
.opciones-grandes .opcion-titulo { font-size: var(--fs-md); font-weight: 700; }
.opciones-grandes .opcion:hover { border-color: var(--brand); background: var(--bg-hover); }

.botonera { flex-wrap: wrap; justify-content: flex-end; align-items: center; }

/* El renglon del resultado de "Probar" no ocupa lugar hasta que hay algo. */
.prueba:empty { display: none; }
/* Un checkbox dentro de la grilla de campos se alinea con la base de los
   inputs de al lado, no con el borde de arriba. */
.form-grid .check { align-self: end; margin-bottom: calc(var(--s4) + 2px); }

/* Los graficos del MikroTik son GIF con fondo blanco: en tema oscuro se
   invierten para que no sean un cartel de luz en medio de la pantalla. El
   hue-rotate devuelve los colores de las series a su tono original. */
[data-theme="dark"] .grafico-red img,
[data-theme="dark"] .graficos img,
[data-theme="dark"] #lupa-img {
  filter: invert(1) hue-rotate(180deg);
}

/* ------------------------------------------- utilidades de las pantallas
   de netmon. Son los style= que traian sus plantillas: aca son clases
   porque la CSP de este panel no permite estilos en el HTML. */
.chico { font-size: var(--fs-sm); }
.mini { font-size: var(--fs-xs); }
.versalita { text-transform: uppercase; letter-spacing: .06em; }
.normal { white-space: normal; }
.arriba { align-items: start; }
.auto { width: auto; min-width: 150px; }
.ancho-260 { min-width: 260px; }
.ancho-280 { max-width: 280px; }
.deslizador { width: 180px; accent-color: var(--brand); }
.mb { margin-bottom: var(--s4); }
.mb-grande { margin-bottom: var(--s6); }
.mt { margin-top: var(--s3); }
.mt-chico { margin-top: var(--s2); }
.sin-margen { margin: 0; }
.sin-margen-arriba { margin-top: 0; }
.titulo-seccion { margin: var(--s6) 0 var(--s3); }
.linea { display: inline-flex; align-items: center; gap: var(--s2); }
.linea.ancha { gap: var(--s3); flex-wrap: wrap; }
.fila-entre { display: flex; justify-content: space-between; align-items: center; }
.renglon { padding: 6px 0; border-bottom: 1px solid var(--line-soft); font-size: var(--fs-sm); }
.fila-lista { margin: 2px 0; }

/* ---------------------------------------- pantallas de netmon
   Las reglas propias de sus pantallas (matriz de destinos, tarjetas de
   origen, etiquetas de region). Vienen de netmon tal cual: los dos
   paneles comparten las mismas variables de color.
*/
/* ------------------------------------------------ tarjetas de origen */
.org-grid {
  display: grid; gap: var(--s3); margin-bottom: var(--s6);
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
}
.org-card {
  background: var(--bg-card); border: 1px solid var(--line-soft);
  border-top: 3px solid var(--oc, var(--ink-faint));
  border-radius: var(--radius); padding: var(--s3) var(--s4);
  box-shadow: var(--shadow-sm);
}
.org-card.alerta { border-color: var(--danger); border-top-color: var(--danger); }
.org-card .cab { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.org-card .nombre { font-weight: 600; font-size: var(--fs-md); }
.org-card .ip { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); }
.org-card .salida {
  font-size: 12px; margin: 6px 0 8px; color: var(--ink-muted);
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
}
.org-card .salida .mono { font-size: 12px; }
.org-card .metricas { display: grid; grid-template-columns: 1fr 1fr; gap: 6px 12px; }
.org-card .metricas div { display: flex; flex-direction: column; line-height: 1.25; }
.org-card .metricas span {
  font-size: 12px;
  color: var(--ink-faint); font-weight: 600;
}
.org-card .metricas b { font-family: var(--ui); font-size: var(--fs-sm); font-weight: 600; }
.org-card .avisos {
  margin: 8px 0 0; padding: 6px 8px; border-radius: 6px;
  background: var(--danger-bg); color: var(--danger); font-size: 12px; line-height: 1.4;
}
.org-card .avisos div + div { margin-top: 3px; }
.oswatch {
  display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  flex-shrink: 0; background: var(--oc, var(--ink-faint));
}
.kindtag {
  font-family: var(--mono); font-size: 12px; font-weight: 700; letter-spacing: .05em;
  text-transform: uppercase; padding: 1px 6px; border-radius: 4px;
  border: 1px solid var(--line-strong); color: var(--ink-faint);
}
/* ------------------------------------------------ matriz origen x destino
   Filas = destinos, columnas = origenes. Es la pregunta que responde esta
   app: como se ve el mundo desde cada uplink. */
.mx-grupo { margin-bottom: var(--s6); }
.mx-grupo > header {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-bottom: var(--s2);
}
.mx-grupo h2 {
  margin: 0; font-size: var(--fs-sm); font-weight: 700;
 
  display: flex; align-items: center; gap: var(--s2);
}
.mx-grupo .umbral { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); }
/* Los grupos son tablas distintas, pero se leen como una sola: si cada una
   reparte el ancho por su cuenta, la columna de un uplink cae en un lugar
   distinto en cada grupo y comparar obliga a buscar. Con el ancho fijo, el
   destino y cada uplink miden lo mismo en General, Gaming y Locales.
   El min-width evita que en pantalla angosta se aplasten: prefiere scrollear. */
.mx {
  width: 100%; min-width: 620px; table-layout: fixed;
  border-collapse: separate; border-spacing: 0; font-size: var(--fs-sm);
}
.mx thead th:first-child, .mx td.dest { width: 240px; }
.mx thead th {
  position: static; background: var(--bg-sunken); text-align: center;
  font-size: 12px; padding: var(--s2) var(--s3);
}
.mx thead th:first-child { text-align: left; }
.mx thead th .th-org { display: inline-flex; align-items: center; gap: 6px; }
.mx td { padding: 6px var(--s3); border-bottom: 1px solid var(--line-soft); }
.mx td.dest { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mx td.dest .host { font-family: var(--mono); font-size: 12px; color: var(--ink-faint); }
.mx td.dest { cursor: pointer; }
/* Mini grafico del destino: las ultimas horas, una linea finita por uplink.
   Discreto a proposito: la forma, no los numeros (esos estan en las celdas). */
.mini-dest { display: block; width: 100%; max-width: 200px; height: 16px; margin-top: 3px; }
.mini-linea { fill: none; stroke-width: 1.1; vector-effect: non-scaling-stroke; opacity: .7; }
.mo0 { stroke: var(--ink-faint); }
.mo1 { stroke: var(--o1); } .mo2 { stroke: var(--o2); } .mo3 { stroke: var(--o3); } .mo4 { stroke: var(--o4); }
.mo5 { stroke: var(--o5); } .mo6 { stroke: var(--o6); } .mo7 { stroke: var(--o7); } .mo8 { stroke: var(--o8); }

/* Leyenda del historico: el cuadradito de color es el tilde. Lleno = se ve,
   vacio (solo el borde) = oculto. Mismo tamaño que el cuadradito de siempre. */
.serie-tildable { cursor: pointer; user-select: none; }
.serie-tilde {
  appearance: none; -webkit-appearance: none; margin: 0; flex-shrink: 0;
  width: 10px; height: 10px; border-radius: 2px; cursor: pointer;
  border: 1.5px solid var(--c, var(--ink-faint)); background: var(--c, var(--ink-faint));
}
.serie-tilde:not(:checked) { background: transparent; }
.serie-tilde:not(:checked) + .faint { text-decoration: line-through; opacity: .6; }
.serie-tilde:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.mx td.celda {
  text-align: center; font-family: var(--mono); cursor: pointer;
  border-left: 1px solid var(--line-soft); min-width: 92px;
  transition: filter .1s;
}
.mx td.celda:hover { filter: brightness(0.96); }
.celda .v { font-size: 14px; font-weight: 500; }
.celda .v small { font-size: 12px; color: var(--ink-muted); font-weight: 400; }
.celda .l { font-size: 12px; color: var(--ink-faint); }
.celda.ok   { background: var(--ok-bg); }
.celda.warn { background: var(--warn-bg); }
.celda.crit { background: var(--danger-bg); }
.celda.ok .v   { color: var(--ok); }
.celda.warn .v { color: var(--warn); }
.celda.crit .v { color: var(--danger); }
.celda.mudo { color: var(--ink-faint); }
/* Grupos sin umbral (locales, extras): el numero y nada mas. */
.celda.neutral .v { color: var(--ink); }
.celda.neutral.caido .v { color: var(--ink-muted); }
/* El mejor uplink de cada fila. Es la decision que se toma con esta tabla:
   por donde conviene mandar el trafico de ese destino. */
.celda.mejor { box-shadow: inset 0 0 0 2px var(--ok); }
.celda .mejor-tag {
  display: block; font-family: var(--ui); font-size: 12px; font-weight: 700;
  color: var(--ok);
}

/* Una fila de umbrales por pais en el alta de destinos. */
.fila-pais {
  display: grid; grid-template-columns: 110px 1fr 1fr auto;
  gap: var(--s2); margin-bottom: 6px; align-items: center;
}

/* Leyenda de los graficos: el color del cuadrito lo pone pintarSeries(). */
.serie { display: inline-flex; align-items: center; gap: 6px; margin-right: var(--s4); }
.serie-cuadro { width: 10px; height: 10px; border-radius: 2px; display: inline-block; }
.serie .faint { font-size: var(--fs-sm); }
.serie-marca { font-size: var(--fs-sm); }

/* Paleta de origenes de netmon: ocho tonos validados para daltonismo, que se
   distinguen en claro y en oscuro. Las usan los graficos (stroke var(--oN)) y
   las tarjetas de origen, asi que sin esto las lineas quedan sin color, que fue
   exactamente lo que paso al portar las pantallas. */
:root {
  --o1: #2a78d6; --o2: #eb6834; --o3: #1baf7a; --o4: #eda100;
  --o5: #e87ba4; --o6: #008300; --o7: #4a3aa7; --o8: #e34948;
}
[data-theme="dark"] {
  --o1: #3987e5; --o2: #d95926; --o3: #199e70; --o4: #c98500;
  --o5: #d55181; --o6: #008300; --o7: #9085e9; --o8: #e66767;
}

/* --------------------------------------------------- como se entra (2FA)
   El QR, los codigos de papel y el "o" que separa los dos caminos del login. */

.o-bien {
  display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap;
  margin-top: var(--s4); color: var(--ink-faint); font-size: var(--fs-sm);
}
/* Las dos rayitas a los costados del "o", sin un solo div de adorno. */
.o-bien::before, .o-bien::after {
  content: ""; flex: 1; height: 1px; background: var(--line-soft);
}
.o-bien span { flex-shrink: 0; }
.o-bien button { flex: 0 0 100%; order: 1; margin-top: var(--s3); }

/* Fondo blanco fijo: el QR se lee por contraste, y en tema oscuro un SVG
   negro sobre negro no lo lee ningun telefono. */
.qr {
  display: inline-block; padding: var(--s3); margin-bottom: var(--s3);
  background: #fff; border: 1px solid var(--line-soft); border-radius: var(--radius-sm);
}
.qr svg { display: block; width: 188px; height: 188px; }

.codigos {
  display: grid; grid-template-columns: 1fr 1fr; gap: 6px var(--s4);
  margin: var(--s4) 0; font-size: var(--fs-md); letter-spacing: .04em;
}
.codigos div {
  padding: 5px var(--s2); background: var(--bg-sunken);
  border-radius: var(--radius-sm); text-align: center;
}

/* ------------------------------------------------------- cuenta corriente
   Un movimiento anulado no se borra: se ve, apagado y tachado, para que se
   entienda que estuvo y ya no cuenta. */
tr.anulado td { opacity: .55; }
tr.anulado .num { text-decoration: line-through; }
.ok-texto { color: var(--ok); }

/* ------------------------------------------------------------------ chips
   Opciones que se eligen de un toque, sin abrir un desplegable. Son pocas y
   se ven todas juntas: en el celular la diferencia es enorme. */
.chips { display: flex; flex-wrap: wrap; gap: 6px; }
.chip {
  height: var(--ctl-h-sm); padding: 0 14px; min-width: 0;
  border-radius: 999px; font-size: var(--fs-sm);
}
.chip.elegido {
  background: var(--brand); border-color: var(--brand); color: var(--on-brand); font-weight: 600;
}
.chip.elegido:hover { background: var(--brand-dark); border-color: var(--brand-dark); color: var(--on-brand); }

/* Los botones de una fila de tabla, juntos y sin que la fila crezca. */
.acciones-fila { display: flex; gap: 6px; justify-content: flex-end; }

/* --------------------------------------------------- ordenar por columna
   El titulo se aprieta y ordena. La flecha aparece solo en la columna por la
   que se esta ordenando; en las demas queda un lugar reservado, asi la fila
   de titulos no se mueve cuando cambias de columna. */
th.ordenable { cursor: pointer; user-select: none; white-space: nowrap; }
th.ordenable:hover { color: var(--ink); background: var(--bg-hover); }
th.ordenable::after {
  content: "↑"; margin-left: 5px; opacity: 0; font-size: 10px;
}
th.ordenable.ordenando { color: var(--ink); }
th.ordenable.ordenando::after { opacity: 1; }
th.ordenable.ordenando.desc::after { content: "↓"; }
th.ordenable:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }

/* Un cuadro que ademas lleva a la lista. Se nota que se puede apretar al
   pasar por encima; sin eso, un numero clickeable no invita a nada. */
a.metric-tile { text-decoration: none; color: inherit; display: block; }
/* Una aclaracion larga ("con instalaciones pendientes y leads") va abajo del
   numero y se parte en renglones: en la capsula de al lado no se corta y se
   salia de la tarjeta. */
.metric-tile { min-width: 0; }
/* En el celular las tarjetas van de a dos (164px): "-$1.443.865" en 24px no
   entra. Mas especifico que los temas, que ponen su propio tamaño. */
@media (max-width: 480px) {
  .tiles .metric-tile .value { font-size: 18px; }
}
.metric-tile .nota-tile {
  margin-top: 4px; font-size: var(--fs-xs); color: var(--ink-muted); line-height: 1.35;
  overflow-wrap: anywhere;
}
a.metric-tile:hover {
  border-color: var(--brand); box-shadow: var(--shadow-md); text-decoration: none;
}
a.metric-tile:hover .value small { color: var(--brand); }

/* ------------------------------------------------ cobranza: WhatsApp */
.respuestas { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--s3); margin-bottom: var(--s6); }
.respuesta { background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s3) var(--s4); display: flex;
  flex-direction: column; gap: var(--s2); }
.resp-cab { display: flex; justify-content: space-between; gap: var(--s2); flex-wrap: wrap;
  align-items: baseline; }
.resp-msgs { display: flex; flex-direction: column; gap: 4px; }
.resp-msg { background: var(--bg-sunken); border-radius: 8px; padding: 4px 8px;
  font-size: var(--fs-sm); white-space: pre-wrap; word-break: break-word; }
.resp-msg .faint { font-size: var(--fs-xs); margin-right: 4px; }
.resp-lectura { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; font-size: var(--fs-sm); }
/* max-width: el width:100% global de los inputs gana por especificidad. */
.resp-lectura input.resp-fecha { max-width: 160px; flex: 0 0 auto; min-height: 0;
  padding: 3px 6px; font-size: var(--fs-sm); }

.modal-bg .modal.modal-rec { max-width: 1040px; }
.rec-cuerpo { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: var(--s4);
  align-items: start; }
.rec-tabla { max-height: 60vh; overflow: auto; }
.rec-tabla td, .rec-tabla th { vertical-align: middle; }
.rec-tabla input.rec-dato { min-width: 70px; max-width: 140px; padding: 4px 6px; min-height: 0; }
.rec-fila { cursor: pointer; }
.rec-fila.activa td { background: var(--brand-bg); }
.rec-falta { color: var(--warn); font-size: var(--fs-sm); }
.rec-estado { font-size: var(--fs-sm); max-width: 220px; }
.rec-previa-caja { position: sticky; top: 0; }
/* La burbuja verde de WhatsApp, para ver el mensaje como le llega. */
.burbuja { background: #dcf8c6; color: #111b21; border-radius: 10px 10px 10px 2px;
  padding: 8px 10px; margin-top: 6px; white-space: pre-wrap; word-break: break-word;
  font-size: var(--fs-sm); line-height: 1.45; box-shadow: var(--shadow-md); }
[data-theme="dark"] .burbuja { background: #005c4b; color: #e9edef; }
@media (max-width: 760px) {
  .rec-cuerpo { grid-template-columns: 1fr; }
  .rec-previa-caja { position: static; order: -1; }
  .respuestas { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------- alertas de la red */
.alertas { display: grid; grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
  gap: var(--s3); margin-bottom: var(--s6); }
.alerta { background: var(--bg-card); border: 1px solid var(--line-soft);
  border-left: 3px solid var(--warn); border-radius: var(--radius);
  padding: var(--s3) var(--s4); display: flex; flex-direction: column; gap: var(--s2); }
.alerta.critico { border-left-color: var(--danger); }
.alerta-cab { display: flex; justify-content: space-between; gap: var(--s2);
  flex-wrap: wrap; align-items: baseline; }
.alerta-pie { display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
  border-top: 1px solid var(--line-soft); padding-top: var(--s2); }
.alerta-pie .faint { margin-right: auto; word-break: break-all; }
@media (max-width: 760px) { .alertas { grid-template-columns: 1fr; } }

/* Cerca del umbral: una barra por cosa que esta por disparar. */
.cercanos { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--s3); margin-bottom: var(--s6); }
.cerca { background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s3) var(--s4); }
.cerca-cab { display: flex; gap: var(--s2); align-items: baseline; margin-bottom: 6px; }
.cerca-cab .cerca-num { margin-left: auto; font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm); }
.cerca .barra { margin: 4px 0 6px; height: 6px; border-radius: 3px; overflow: hidden;
  background: var(--bg-sunken); }
.cerca .barra span { display: block; height: 100%; background: var(--brand); }
.cerca .barra span.casi { background: var(--warn); }
@media (max-width: 760px) { .cercanos { grid-template-columns: 1fr; } }

/* Los nodos propios, abajo del casillero de "a quién va este equipo". */
.chips-nodos { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 4px; }
.chips-nodos button { padding: 1px 6px; font-size: var(--fs-xs); min-height: 0; }

/* ------------------------------------------------- Detalles: lo que falta */
.detalle-fila { background: var(--bg-card); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: var(--s3) var(--s4); margin-bottom: var(--s3); }
.detalle-fila.sin-titular { border-left: 3px solid var(--danger); }
.detalle-cab { display: flex; flex-wrap: wrap; gap: var(--s2); align-items: baseline;
  margin-bottom: var(--s2); }
.detalle-cab .faint:last-child { margin-left: auto; }
.detalle-campos { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: var(--s2); }
.detalle-campo { display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  border-top: 1px solid var(--line-soft); padding-top: 6px; }
.detalle-campo label { flex: 1 0 100%; font-size: var(--fs-xs); color: var(--ink-muted); }
.detalle-campo input { flex: 1 1 140px; max-width: 220px; min-height: 0; padding: 4px 6px; }

/* Planes: un plan que ya no se vende sigue estando, pero apagado. */
tr.plan-inactivo td { opacity: .55; }
tr.plan-inactivo input { opacity: .8; }
.tilde { display: inline-flex; align-items: center; gap: 4px; font-size: var(--fs-sm); }
#tabla input.angosto { width: 5.5em; text-align: right; }

/* ------------------------------------------------ conexiones en el celular
   La tabla mide mas que la pantalla y se scrollea al costado. La ficha vivia
   adentro de esa tabla, asi que para leerla habia que ir y volver de costado
   en cada renglon. Ahora se queda pegada al borde izquierdo y mide lo que mide
   la pantalla: se abre y se lee, sin moverse. */
@media (max-width: 760px) {
  .tabla-conexiones tr.ficha > td { padding: var(--s3) 0; }
  .tabla-conexiones .ficha-cont {
    position: sticky; left: 0;
    width: calc(100vw - 2 * var(--s4)); padding: 0 var(--s4);
  }
  /* Etiqueta y valor en la misma linea: en un renglon cada uno, la ficha
     medía tres pantallas de alto. */
  .detalle-conexion .par {
    display: flex; justify-content: space-between; align-items: baseline; gap: var(--s3);
  }
  .detalle-conexion .par .k { margin: 0; flex: 0 0 auto; }
  .detalle-conexion .par .v { justify-content: flex-end; text-align: right; min-height: 0; }
  .detalle-cols { gap: var(--s4); }
  /* Lo que no identifica al cliente se lee en la ficha, no en la tabla. */
  .tabla-conexiones th:nth-child(6), .tabla-conexiones td:nth-child(6),
  .tabla-conexiones th:nth-child(7), .tabla-conexiones td:nth-child(7),
  .tabla-conexiones th:nth-child(9), .tabla-conexiones td:nth-child(9) { display: none; }
  /* Y lo que queda entra en la pantalla: menos aire, y el nombre puede partirse
     en dos renglones antes que empujar la tabla de costado. */
  .tabla-conexiones th, .tabla-conexiones tbody td { padding-left: 6px; padding-right: 6px; }
  .tabla-conexiones th, .tabla-conexiones td:not(.num) { white-space: normal; }
  .tabla-conexiones td.expandir, .tabla-conexiones th.expandir { width: 34px; }
}
@media (max-width: 560px) {
  .tabla-conexiones th:nth-child(5), .tabla-conexiones td:nth-child(5),
  .tabla-conexiones th:nth-child(8), .tabla-conexiones td:nth-child(8) { display: none; }
}

/* Planes: la fila con cambios sin guardar se ve, para no irse creyendo que ya
   quedó guardado. */
tr.sin-guardar td { background: var(--warn-bg); }

/* Uplinks: el uplink destildado sigue en la lista, apagado. */
.org-card.apagado { opacity: .5; }
.org-card .serie-tildable { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; }

/* Tuneles (Vultr): se miden, pero no son uplinks. Borde punteado y sin rojo. */
.org-card.tunel { border-style: dashed; }
.org-card.tunel.alerta { border-color: var(--warn); border-top-color: var(--warn); }
.uc-sep { font-size: 12px; opacity: .7; margin-top: 6px; border-top: 1px dashed var(--line-strong); padding-top: 4px; }
.uc-tunel { opacity: .8; }

/* Ruteo automatico. */
.negrita { font-weight: 600; }
.texto-rojo { color: var(--danger); }
/* Tablero por uplink: una columna por salida y cada grupo como ficha en la
   columna por donde sale. En el celular las columnas se apilan. */
.ruteo-tablero { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--s3); }
.ruteo-col { background: var(--bg-sunken); border: 1px solid var(--line-soft); border-radius: var(--radius);
             padding: var(--s3); display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.ruteo-col.vacia { opacity: .75; }
.ruteo-col header { display: flex; flex-direction: column; gap: 2px; margin-bottom: 4px; }
.ruteo-col h3 { margin: 0; font-size: var(--fs-md); }
.ruteo-ficha { display: flex; flex-direction: column; align-items: flex-start; gap: 6px; text-align: left;
               width: 100%; padding: var(--s2) var(--s3); background: var(--bg-raised);
               border: 1px solid var(--line-strong); border-radius: var(--radius-sm); cursor: pointer;
               color: var(--ink); height: auto; white-space: normal; }
.ruteo-ficha:hover { border-color: var(--brand); }
.ruteo-ficha-cab { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.ruteo-barras { display: flex; flex-direction: column; gap: 8px; margin: var(--s3) 0; }
.ruteo-barra { display: grid; grid-template-columns: minmax(120px, 30%) 1fr minmax(150px, 35%);
               gap: var(--s2); align-items: center; }
.ruteo-barra.fuera { opacity: .6; }
.ruteo-barra-pista { height: 10px; background: var(--bg-sunken); border-radius: 5px; overflow: hidden; }
.ruteo-barra-relleno { display: block; height: 100%; width: 0; background: var(--ink-faint); border-radius: 5px; }
.ruteo-barra-relleno.mejor { background: var(--ok); }
.ruteo-destinos { max-height: 180px; overflow-y: auto; border: 1px solid var(--line-soft); border-radius: var(--radius-sm);
                  padding: var(--s2); margin-bottom: var(--s3); }
.ruteo-destinos .check { margin-bottom: 4px; }
table.ruteo-importar.solo-medibles tr.sin-destino { display: none; }
table.ruteo-importar input[type=text] { min-width: 140px; }
@media (max-width: 640px) {
  .ruteo-barra { grid-template-columns: 1fr; gap: 2px; }
}

/* Topologia: diagrama SVG de salidas, POPs y enlaces. Los colores van por
   clase (la CSP no deja style= en el HTML). */
/* El lienzo tiene alto fijo: adentro se arrastra y se hace zoom, la pagina no
   se mueve. touch-action none para que el dedo mueva el diagrama y no la hoja. */
.topo-lienzo { height: min(62vh, 620px); min-height: 340px; border: 1px solid var(--line-soft);
               border-radius: var(--radius-sm); background: var(--bg-sunken); overflow: hidden; }
.topo-svg { display: block; width: 100%; height: 100%; touch-action: none; cursor: grab; user-select: none; }
.topo-svg:active { cursor: grabbing; }
.topo-barra { align-items: flex-start; gap: var(--s2); margin-bottom: var(--s2); }
.topo-pestanias { display: flex; gap: 2px; border-bottom: 1px solid var(--line-strong); margin: var(--s3) 0; overflow-x: auto; }
.topo-pestanias button { background: none; border: 0; border-bottom: 2px solid transparent; border-radius: 0;
                         padding: 6px 12px; color: var(--ink-faint); white-space: nowrap; height: auto; }
.topo-pestanias button.activa { color: var(--ink); border-bottom-color: var(--brand); font-weight: 600; }
.topo-grupo { margin-bottom: var(--s2); }
.topo-grupo summary { cursor: pointer; padding: 4px 0; }
.topo-cli-fila { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.topo-cli-fila > span:first-child { flex: 1 1 160px; }
.topo-detalle .par { display: flex; gap: var(--s3); padding: 3px 0; }
.topo-detalle .par .k { color: var(--ink-faint); min-width: 80px; }
.topo-nodo { cursor: pointer; }
.topo-nodo rect { fill: var(--bg-raised); stroke: var(--line-strong); stroke-width: 1.5; }
.topo-nodo.salida rect { fill: var(--brand-bg); stroke: var(--brand); stroke-width: 2; }
.topo-nodo.relevo rect { stroke-dasharray: 5 3; }
.topo-nodo.elegido rect { stroke: var(--brand); stroke-width: 3; }
.topo-nodo:hover rect, .topo-nodo:focus rect { stroke: var(--brand); }
.topo-nodo:focus { outline: none; }
.topo-nombre { fill: var(--ink); font-size: 13px; font-weight: 600; }
.topo-sub { fill: var(--ink-faint); font-size: 11px; }
.topo-alerta { fill: var(--danger); }
.topo-enlace { cursor: pointer; }
.topo-linea { fill: none; stroke: var(--ink-faint); stroke-width: 2; }
.topo-toque { fill: none; stroke: transparent; stroke-width: 12; }
.topo-enlace.respaldo .topo-linea { stroke: var(--brand); stroke-dasharray: 7 5; }
.topo-enlace.sin-medio .topo-linea { stroke: var(--warn); stroke-dasharray: 2 4; }
.topo-enlace.caido .topo-linea { stroke: var(--danger); }
.topo-enlace:hover .topo-linea { stroke-width: 3.5; }
.topo-etiqueta { fill: var(--ink-faint); font-size: 10px; paint-order: stroke; stroke: var(--bg-raised);
                 stroke-width: 3px; }
.topo-leyenda { display: flex; flex-wrap: wrap; gap: var(--s3); margin-bottom: var(--s2); }
.topo-leyenda span { display: inline-flex; align-items: center; gap: 6px; }
.topo-muestra { display: inline-block; width: 26px; border-top: 2px solid var(--ink-faint); }
.topo-muestra.respaldo { border-top: 2px dashed var(--brand); }
.topo-muestra.sin-medio { border-top: 2px dotted var(--warn); }
.topo-muestra.caido { border-top-color: var(--danger); }
.topo-caja { display: inline-block; width: 18px; height: 12px; border: 1.5px solid var(--line-strong);
             border-radius: 3px; }
.topo-caja.salida { background: var(--brand-bg); border-color: var(--brand); }
.topo-caja.relevo { border-style: dashed; }
.topo-lista { list-style: none; padding: 0; margin: 0 0 var(--s3); }
.topo-lista li { padding: 3px 0; border-bottom: 1px solid var(--line-soft); }
button.link { background: none; border: 0; padding: 0; height: auto; color: var(--brand); cursor: pointer;
              font: inherit; text-decoration: underline; }
.topo-nodo.punto rect { fill: var(--bg-sunken); stroke-dasharray: 2 3; }
.topo-caja.punto { background: var(--bg-sunken); border-style: dotted; }
.topo-luz { font-size: 12px; fill: var(--warn); }
.topo-luz.todas { fill: var(--danger); }
.topo-luz-muestra { color: var(--warn); }
/* El puntito para agarrar un enlace: se ve al pasar por arriba, o siempre si
   ya se lo acomodo a mano. */
.topo-codo { fill: var(--bg-raised); stroke: var(--ink-faint); stroke-width: 1.5; opacity: 0; }
.topo-codo.fijo { opacity: .7; }
.topo-enlace:hover .topo-codo { opacity: 1; stroke: var(--brand); }
.topo-barra { display: flex; flex-wrap: wrap; justify-content: flex-start; }
.topo-barra .rango { flex-wrap: wrap; }
.topo-leyendas .topo-leyenda { margin-bottom: var(--s2); }

/* Mapa (web/mapa.js): tiles de OSM por el panel y una capa SVG encima. */
.mapa { position: relative; overflow: hidden; touch-action: none; cursor: grab; user-select: none;
        background: #e8e6e1; }
.mapa:active { cursor: grabbing; }
.mapa-tiles, .mapa-svg { position: absolute; inset: 0; }
.mapa-tiles img { position: absolute; width: 256px; height: 256px; max-width: none; pointer-events: none; }
[data-theme="dark"] .mapa-tiles img { filter: invert(1) hue-rotate(180deg) brightness(.85) contrast(.9); }
.mapa-atribucion { position: absolute; right: 4px; bottom: 4px; font-size: 12px; padding: 1px 5px;
                   background: rgba(255,255,255,.8); color: #333; border-radius: 3px; }
.mapa-barra { position: absolute; left: 8px; right: 8px; top: 8px; padding: 6px 10px; border-radius: var(--radius-sm);
              background: var(--bg-raised); border: 1px solid var(--line-strong); font-size: var(--fs-sm);
              display: flex; flex-wrap: wrap; gap: 6px; align-items: center; box-shadow: 0 2px 8px rgba(0,0,0,.15); }
.mapa-barra > div { flex-basis: 100%; }
.mapa-linea { fill: none; stroke-width: 3.5; stroke-linecap: round; stroke-linejoin: round; }
.mapa-toque { fill: none; stroke: transparent; stroke-width: 16; cursor: pointer; }
.mapa-enlace.radio .mapa-linea { stroke: #2b7cd3; }
.mapa-enlace.fibra .mapa-linea { stroke: #d9730d; stroke-width: 4; }
.mapa-enlace.cable .mapa-linea { stroke: #4a5568; }
.mapa-enlace.sin-medio .mapa-linea { stroke: var(--warn); stroke-dasharray: 3 5; }
.mapa-enlace.respaldo .mapa-linea { stroke-dasharray: 9 6; }
.mapa-enlace.caido .mapa-linea { stroke: var(--danger); }
.mapa-enlace.elegido .mapa-linea { stroke-width: 6; }
.mapa-vertice { fill: #fff; stroke: #d9730d; stroke-width: 3; cursor: move; }
.mapa-nodo { cursor: pointer; }
.mapa-nodo circle, .mapa-nodo rect { fill: #fff; stroke: #1f2937; stroke-width: 2.5; }
.mapa-nodo.salida circle { fill: #2b7cd3; stroke: #fff; }
.mapa-nodo.relevo circle { stroke-dasharray: 3 2; }
.mapa-nodo.punto { cursor: move; }
.mapa-nodo.punto rect { fill: #d9730d; stroke: #fff; }
.mapa-nodo.elegido circle, .mapa-nodo.elegido rect { stroke: #2b7cd3; stroke-width: 4; }
.mapa-nombre { font-size: 12px; font-weight: 600; fill: #111; paint-order: stroke; stroke: #fff; stroke-width: 3px; }
.topo-muestra.mapa-radio { border-top: 3px solid #2b7cd3; }
.topo-muestra.mapa-fibra { border-top: 3px solid #d9730d; }
.topo-muestra.mapa-cable { border-top: 3px solid #4a5568; }

/* El logo de la barra lleva al tablero. */
a.brand { color: inherit; text-decoration: none; }
a.brand:hover .name { color: var(--brand); }
/* Respaldo inalambrico en el mapa: amarillo, para distinguirlo del principal. */
.mapa-enlace.radio.respaldo .mapa-linea { stroke: #e0b000; }
.topo-muestra.mapa-radio-respaldo { border-top: 3px dashed #e0b000; }
/* Switch compartido: violeta, para que no se confunda con un punto de la red;
   el cable a cada casa, fino y punteado, y la casa como un puntito. */
.mapa-nodo.punto.compartido rect { fill: #7c3aed; }
.mapa-enlace.a-cliente .mapa-linea { stroke: #7c3aed; stroke-width: 2.5; stroke-dasharray: 6 4; }
.mapa-enlace.a-cliente.elegido .mapa-linea { stroke-width: 4.5; }
.topo-muestra.mapa-a-cliente { border-top: 3px dashed #7c3aed; }
.mapa-casa circle { fill: #7c3aed; stroke: #fff; stroke-width: 2; }
.mapa-casa.desconectada circle { fill: #9ca3af; }
/* Sobre la foto satelital las lineas finas se pierden: un borde blanco las
   separa del fondo. La foto no se invierte en el tema oscuro. */
.mapa.satelital .mapa-linea { filter: drop-shadow(0 0 1.5px rgba(255, 255, 255, .9)); }
[data-theme="dark"] .mapa.satelital .mapa-tiles img { filter: none; }

/* La copia de solo lectura (mon.renet.com.ar): una franja fija abajo, sobre
   todo lo demas, con la hora de la copia. Abajo y no arriba porque arriba
   esta el menu fijo; el menu y la pagina se corren para no quedar tapados. */
.franja-solo-lectura {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 200;
  height: 32px; line-height: 32px; padding: 0 var(--s4);
  overflow: hidden; white-space: nowrap; text-overflow: ellipsis;
  text-align: center; font-weight: 600; font-size: var(--fs-sm, 13px);
  color: var(--warn); background: var(--bg-raised);
  border-top: 2px solid var(--warn);
}
body.solo-lectura { padding-bottom: 32px; }
body.solo-lectura .sidebar { bottom: 32px; }

/* ------------------------------------------------------------------ logs
   Una linea por evento, con la hora adelante en mono para que se lean en
   columna. "En columnas" pone MikroTik, UISP y el panel lado a lado: sirve
   para ver que paso en los tres a la misma hora; en el celular se apilan. */
.logs-filtros { align-items: center; }
.logs-filtros .field { min-width: 220px; flex: 1; }
.log-lista {
  border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--bg-raised); overflow: hidden;
}
.log-linea {
  display: flex; align-items: baseline; gap: var(--s2);
  padding: 5px var(--s3); border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-sm); line-height: 1.4;
}
.log-linea:last-child { border-bottom: none; }
.log-linea.nivel-error { box-shadow: inset 3px 0 0 var(--danger); }
.log-linea.nivel-aviso { box-shadow: inset 3px 0 0 var(--warn); }
.log-hora { color: var(--ink-faint); font-size: var(--fs-xs); white-space: nowrap; flex-shrink: 0; }
.log-fuente {
  font-family: var(--mono); font-size: var(--fs-xs); color: var(--ink-muted);
  min-width: 62px; flex-shrink: 0;
}
.log-linea .badge { padding: 0 7px; flex-shrink: 0; }
.log-msj { min-width: 0; overflow-wrap: anywhere; }
.log-columnas { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s3); }
.log-columna h3 { font-size: var(--fs-sm); margin: 0 0 var(--s2); }
.log-columna .log-lista { max-height: 70vh; overflow-y: auto; }
.log-columna .log-linea { flex-wrap: wrap; row-gap: 0; }
@media (max-width: 1100px) {
  .log-columnas { grid-template-columns: 1fr; }
  .log-columna .log-lista { max-height: none; }
}

/* Servicios: la tira de 24 h de cada fila y su globo, que en 144 px no entra
   en la esquina como en los graficos grandes: va arriba de la tira. */
.tira-celda { display: flex; align-items: center; gap: var(--s2); white-space: nowrap; }
.tira { position: relative; display: inline-block; line-height: 0; }
.tira .chart-tip {
  top: auto; right: auto; left: 0; bottom: calc(100% + 4px);
  white-space: nowrap; z-index: 5; line-height: 1.5;
}
button.enlace-nombre {
  background: none; border: none; padding: 0; height: auto; min-width: 0;
  color: var(--ink); font: inherit; font-weight: 600; cursor: pointer; text-align: left;
}
button.enlace-nombre:hover { color: var(--brand); text-decoration: underline; }

/* En vivo (mon): lo que ve el vigia. Pensado para el celular: una columna,
   el core arriba y grande, los POPs en tarjetas (lo malo primero). */
.vivo-core {
  border: 2px solid var(--line-strong); border-radius: 10px;
  padding: var(--s4); margin-bottom: var(--s4); background: var(--bg-raised);
}
.vivo-core.ok   { border-color: var(--ok);     background: var(--ok-bg); }
.vivo-core.warn { border-color: var(--warn);   background: var(--warn-bg); }
.vivo-core.crit { border-color: var(--danger); background: var(--danger-bg); }
.vivo-core-titulo { font-size: 1.3rem; font-weight: 700; line-height: 1.3; }
.vivo-core.crit .vivo-core-titulo { color: var(--danger); }
.vivo-equipos, .vivo-pops {
  display: grid; gap: var(--s3);
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
}
.vivo-equipo, .vivo-pop {
  border: 1px solid var(--line-soft); border-radius: 8px;
  padding: var(--s3); background: var(--bg-raised); min-width: 0;
}
.vivo-equipo .badge { margin-left: var(--s2); }
.vivo-pop-cabeza {
  display: flex; justify-content: space-between; align-items: center;
  gap: var(--s2); flex-wrap: wrap;
}
.vivo-eventos { display: flex; flex-direction: column; gap: var(--s2); }
.vivo-evento {
  border-left: 3px solid var(--line-strong); padding: var(--s2) var(--s3);
  background: var(--bg-raised); border-radius: 0 6px 6px 0;
}
.vivo-evento.critico { border-left-color: var(--danger); }
.vivo-evento.aviso   { border-left-color: var(--warn); }
.vivo-evento.normal  { border-left-color: var(--ok); }
.vivo-equipo .faint {
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
@media (max-width: 560px) {
  /* Los equipos, de a dos y chicos: lo que importa en el celular son los POPs. */
  .vivo-equipos { grid-template-columns: 1fr 1fr; gap: var(--s2); }
  .vivo-equipo { padding: var(--s2); }
  .vivo-equipo .badge { margin-left: 0; display: table; margin-top: 2px; }
  .vivo-pops { grid-template-columns: 1fr; }
  .vivo-core-titulo { font-size: 1.15rem; }
}

/* Red -> Trafico: un tablero. Los dos graficos chicos lado a lado en la
   compu y apilados en el celular; en la tabla, una barrita debajo de cada
   numero para comparar a ojo contra el que mas tiene. */
.trafico-cabeza { align-items: flex-end; margin-bottom: var(--s4); }
.trafico-grilla { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); }
@media (max-width: 900px) { .trafico-grilla { grid-template-columns: 1fr; } }
.trafico-barra { align-items: center; gap: var(--s3); margin-bottom: var(--s3); flex-wrap: wrap; }
.trafico-barra h2 { margin: 0; }
.trafico-controles { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }
.trafico-controles input[type=search] { min-width: 220px; margin: 0; }
.trafico-controles .check { margin: 0; }
.tabla-trafico td { vertical-align: top; }
.barrita { display: block; height: 3px; margin-top: 4px; background: var(--bg-sunken); border-radius: 2px; overflow: hidden; }
.barrita i { display: block; height: 100%; width: 0; background: var(--brand); opacity: .75; }
.uso-lleno { color: var(--warn); font-weight: 600; }

/* Switch compartido: la lista de vecinos con su puerto, y las fotos de cada
   nodo. Una 360 es una imagen muy ancha: se ve de 260 px de alto y se
   arrastra de costado. */
.compartido-cercanos { max-height: 280px; overflow-y: auto; }
.compartido-puertos { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 6px var(--s3); }
.compartido-puertos label { display: flex; justify-content: space-between; align-items: center; gap: var(--s2); }
.topo-fotos { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: var(--s3); margin-bottom: var(--s3); }
.topo-fotos.panoramas { grid-template-columns: 1fr; }
.topo-foto { margin: 0; }
.topo-foto img, .topo-foto video { width: 100%; border-radius: var(--radius-sm); display: block; background: var(--bg-sunken); }
.topo-fotos:not(.panoramas) .topo-foto img { aspect-ratio: 4 / 3; object-fit: cover; }
.topo-foto figcaption { display: flex; gap: var(--s2); align-items: baseline; flex-wrap: wrap; margin-top: 4px; }
.panorama { overflow-x: auto; cursor: grab; touch-action: pan-y; border-radius: var(--radius-sm); }
.panorama img { height: 260px; width: auto; max-width: none; }
.topo-subir { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; margin-bottom: var(--s4); }
.topo-subir input[type=text] { max-width: 260px; margin: 0; }

/* Topologia: la lista de POPs se toca para ver el detalle. */
.fila-pop { cursor: pointer; }
.fila-pop:hover { background: var(--bg-sunken); }

/* Que hay cerca (web/cerca.js): el mapa y la lista, lado a lado en la compu. */
.cerca-cuerpo { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--s4); margin-top: var(--s3); }
.cerca-mapa { height: 340px; border-radius: var(--radius-sm); }
.cerca-lista { max-height: 340px; overflow-y: auto; }
.cerca-ul { margin: 0 0 var(--s3); padding-left: 1.1em; }
.cerca-linea { stroke: #d32f2f; stroke-width: 2; stroke-dasharray: 5 4; }
.cerca-nuevo path { fill: #d32f2f; stroke: #fff; stroke-width: 2; }
.cerca-nuevo-ojo { fill: #fff; }
.cerca-cliente circle { stroke: #fff; stroke-width: 2; }
.cerca-cliente.con-sesion circle { fill: #2e9d57; }
.cerca-cliente.sin-sesion circle, .cerca-cliente.pendiente circle { fill: #9ca3af; }
.cerca-pop circle { fill: #2b7cd3; stroke: #fff; stroke-width: 2.5; }
.cerca-pop.off circle { fill: #9ca3af; }
.angosto-cerca { max-width: 460px; }

/* Stock: lo que necesita cada pendiente, en chips (✓ alcanza, ✕ falta), y
   abajo lo que impide dar el plan con las salidas posibles. */
.stock-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.stock-problema { color: var(--danger); font-size: 12px; font-weight: 600; margin-top: var(--s2); }
.stock-opciones { margin: 4px 0 0; padding-left: 18px; font-size: 12px; }
.stock-opciones li { margin-bottom: 4px; }
.stock-opciones .badge { font-size: 12px; }
.botonera-fila { white-space: nowrap; text-align: right; }
.botonera-fila button + button { margin-left: 4px; }

/* Alta: la lista para elegir un cliente que ya existe. */
.lista-existentes { list-style: none; margin: 0; padding: 0; max-height: 50vh; overflow-y: auto; }
.lista-existentes li { display: flex; gap: var(--s2); align-items: baseline; padding: 6px 0;
                       border-bottom: 1px solid var(--line); }

/* El codigo de ingreso a la app: se dicta o se copia, tiene que leerse de lejos. */
.codigo-grande { font-family: var(--mono); font-size: 2rem; font-weight: 700; letter-spacing: .12em;
  text-align: center; padding: 12px; margin: 8px 0 16px; border: 1px dashed var(--line-strong); border-radius: 10px; }
