/* neo — hoja de estilos.
   Identidad corporativa (sección 67-71): #0E1D3C estructura/navegación,
   #F91E51 acciones principales/destacados, #F7F7F9 fondo neutro,
   #FFF6F6 alertas suaves. El resto de la paleta es deliberadamente acotado:
   esto es una consola de ingeniería, no un dashboard decorativo (sección 71).

   Redefinición "Sistema de temas claro/oscuro": el navy YA es oscuro, así
   que la barra superior (`.topbar`) y el panel de log (`.log-viewer-body`,
   `.json-view` — ya un panel "consola" deliberadamente oscuro en ambos
   temas) no cambian entre temas — es lo que permite que el navy y el rosa
   corporativos sigan siendo reconocibles en el tema oscuro sin ningún ajuste:
   simplemente no se tocan. Sólo el FONDO/SUPERFICIES/TEXTO del resto de la
   consola invierte. Estado del tema en el DOM: sin atributo = "Automático"
   (sigue `prefers-color-scheme`); `data-theme="light"`/`"dark"` = elección
   explícita del usuario, siempre gana sobre la preferencia del sistema. */

:root {
  --color-navy: #0e1d3c;
  --color-navy-soft: #16294f;
  --color-pink: #f91e51;
  --color-pink-soft: #fde6ec;
  --color-bg: #f7f7f9;
  --color-alert-bg: #fff6f6;
  --color-surface: #ffffff;
  --color-surface-2: #fafbfe;
  --color-border: #e2e4ea;
  --color-text: #1a1f2b;
  --color-text-muted: #5b6172;
  --color-text-inverse: #f7f7f9;

  --color-available: #1a8a5f;
  --color-preparing: #b7791f;
  /* Redefinición "Colores de estado y secuencia de carga": un único azul para
     los tres estados de la familia "carga en curso" (Charging fijo,
     SuspendedEV/Finishing parpadeantes, ver .state-blink más abajo) — antes
     Charging tenía su propio color (rosa corporativo) y SuspendedEV
     compartía el ámbar de Preparing; ninguno de los dos coincidía con el
     azul pedido. */
  --color-finishing: #2f6fb0;
  --color-faulted: #c62828;
  --color-offline: #6b7280;
  /* Redefinición "Indisponibilidad de conectores DC y comportamiento del
     LED": amarillo propio para "Reservado" — antes reutilizaba el ámbar de
     Preparing, indistinguible de él a simple vista. */
  --color-reserved: #d9a900;
  /* Redefinición "Sistema de temas claro/oscuro...": línea/eje principal de
     las gráficas <canvas> (`chart.js`) — normalmente el mismo navy de marca,
     pero el navy NO cambia con el tema (sigue anclando el `.topbar`); una
     línea de esa misma oscuridad se hundiría contra el fondo oscuro de una
     tarjeta en tema oscuro, así que este token sí varía. Bug real ya
     encontrado una vez: su valor en oscuro NO puede coincidir con
     `--color-finishing` (ambos se usan como color de curva en la misma
     gráfica de tensión DC — "pedida"/"ofrecida" — y con el mismo hex las
     dos curvas se vuelven indistinguibles entre sí). */
  --color-chart-line: #0e1d3c;
  /* Mismo problema que `--color-chart-line` pero para TEXTO de énfasis
     (h1/h2/h3, valores destacados, enlaces, pestaña activa...): en el tema
     claro reutilizaban directamente `--color-navy` porque navy-sobre-blanco
     contrasta perfecto, pero navy NO cambia con el tema — navy-sobre-navy-
     oscuro (tema oscuro) se hunde. Token dedicado, con el mismo valor que
     `--color-navy` en claro (cero cambio visual ahí) y el mismo azul más
     claro que `--color-chart-line` en oscuro (mantiene la MISMA relación
     "azul de marca distinto del texto normal" en ambos temas). */
  --color-heading: #0e1d3c;

  --color-info-bg: #eef4fb;
  --color-info-border: #c9dcf1;
  --color-info-text: #1c4e80;
  --color-alert-border: #f3c8c8;
  --color-pending-border: #e7b9b9;
  --color-pending-text: #7a3a3a;
  --color-tint-success-bg: #e3f5ec;
  --color-tint-neutral-bg: #eceef3;
  /* Redefinición "Flujo de validación de usuarios por administrador...":
     tinte de fondo para el pill de estado "Pendiente" — mismo criterio que
     `--color-tint-success-bg`/`--color-tint-neutral-bg` (un tono muy claro
     del color de estado ya usado para "Preparando"). */
  --color-tint-warning-bg: #faf0dd;
  /* Redefinición "Convierte a un usuario en Super Admin...": tinte + texto
     PROPIOS para el pill (nunca `--color-navy`/`--color-heading` directo)
     — `--color-navy` está documentado arriba como fijo entre temas
     ("simplemente no se toca"), así que en oscuro terminaba casi idéntico
     a `--color-surface` (#141d33 vs #0e1d3c) y el pill se veía "sin
     fondo". Mismo patrón tinte-claro+texto-saturado que `.pill-admin`
     (`--color-pink-soft`/`--color-pink`), no un relleno sólido. */
  --color-super-admin-bg: #e4e7f2;
  --color-super-admin-text: #26325c;

  /* Redefinición "Balance energético global de la plataforma": un color fijo
     por TIPO de conector (nunca por estado — esos ya existen arriba), para
     que un mismo tipo se vea siempre igual en cualquier gráfica de la app.
     Ocho tonos deliberadamente distintos entre sí y de los colores de
     estado de arriba, dentro de la misma familia moderada (nada neón) de
     "consola de ingeniería" ya establecida. */
  --color-connector-type1: #c2410c;
  --color-connector-type2: #f91e51;
  --color-connector-ccs1: #7c3aed;
  --color-connector-ccs2: #0369a1;
  --color-connector-chademo: #0d9488;
  --color-connector-gbt-ac: #65a30d;
  --color-connector-gbt-dc: #a21caf;
  --color-connector-tesla: #57534e;

  /* Redefinición "Ampliación del Log General: eventos TCP/red y auditoría
     de cambios": mismo criterio que los 8 colores de tipo de conector de
     arriba — un tono fijo por naturaleza nueva, independiente del tema,
     elegido para no repetir ningún hue ya usado por las 8 naturalezas
     existentes (`.log-nature-pill`, más abajo) ni por los tipos de
     conector de arriba. */
  --color-nature-tcp: #0891b2;
  --color-nature-audit: #78350f;

  --font-ui: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-mono: ui-monospace, "Cascadia Code", "Segoe UI Mono", "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

  --radius: 6px;
  --shadow-sm: 0 1px 2px rgba(14, 29, 60, 0.08);
  --shadow-md: 0 4px 16px rgba(14, 29, 60, 0.12);
}

/* "Automático": sigue prefers-color-scheme salvo que el usuario haya elegido
   explícitamente "Claro" (data-theme="light" le gana a esto siempre, por eso
   el guard :not([data-theme="light"])). Colores de estado más CLAROS que en
   el tema diurno a propósito: están calibrados para contrastar sobre blanco;
   sobre el fondo oscuro un azul/rojo/verde igual de oscuro se hunde contra
   el navy de fondo y deja de leerse como "estado" a simple vista. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --color-pink-soft: #3a1626;
    --color-bg: #0b1220;
    --color-alert-bg: #2a1414;
    --color-surface: #141d33;
    --color-surface-2: #1c2842;
    --color-border: #2a3854;
    --color-text: #e8eaf0;
    --color-text-muted: #96a1bd;
    /* Sin cambio: sigue siendo el texto legible sobre el navy del `.topbar`,
       que no invierte con el tema — invertirlo aquí lo volvería oscuro
       sobre oscuro. */
    --color-text-inverse: #f7f7f9;

    --color-available: #34c785;
    --color-preparing: #e0a83e;
    --color-finishing: #5b9fe3;
    --color-faulted: #f2564f;
    --color-offline: #8b93a7;
    --color-reserved: #f0c94d;
    --color-chart-line: #c7cfe0;
    --color-heading: #5b9fe3;

    --color-info-bg: #16283d;
    --color-info-border: #2d4a68;
    --color-info-text: #8fc1f2;
    --color-alert-border: #5a2a2a;
    --color-pending-border: #5a2f2f;
    --color-pending-text: #e0a8a0;
    --color-tint-success-bg: #123526;
    --color-tint-neutral-bg: #232d42;
    --color-tint-warning-bg: #3a2c12;
    /* Redefinición "Convierte a un usuario en Super Admin...": fondo más
       CLARO que `--color-surface` (#141d33) para que el chip se distinga de
       la tarjeta que lo contiene — nunca `--color-navy` fijo, que en este
       tema queda casi idéntico a la superficie. */
    --color-super-admin-bg: #2a3b66;
    --color-super-admin-text: #b7c6ef;

    --color-connector-type1: #fb923c;
    --color-connector-type2: #fb7191;
    --color-connector-ccs1: #a78bfa;
    --color-connector-ccs2: #38bdf8;
    --color-connector-chademo: #2dd4bf;
    --color-connector-gbt-ac: #a3e635;
    --color-connector-gbt-dc: #e879f9;
    --color-connector-tesla: #d6d3d1;
  }
}

/* Elección explícita "Oscuro" — mismos valores que el bloque de arriba,
   pero gana siempre (incluso con el sistema operativo en claro). */
:root[data-theme="dark"] {
  --color-pink-soft: #3a1626;
  --color-bg: #0b1220;
  --color-alert-bg: #2a1414;
  --color-surface: #141d33;
  --color-surface-2: #1c2842;
  --color-border: #2a3854;
  --color-text: #e8eaf0;
  --color-text-muted: #96a1bd;
  --color-text-inverse: #f7f7f9;

  --color-available: #34c785;
  --color-preparing: #e0a83e;
  --color-finishing: #5b9fe3;
  --color-faulted: #f2564f;
  --color-offline: #8b93a7;
  --color-reserved: #f0c94d;
  --color-chart-line: #c7cfe0;
  --color-heading: #5b9fe3;

  --color-info-bg: #16283d;
  --color-info-border: #2d4a68;
  --color-info-text: #8fc1f2;
  --color-alert-border: #5a2a2a;
  --color-pending-border: #5a2f2f;
  --color-pending-text: #e0a8a0;
  --color-tint-success-bg: #123526;
  --color-tint-neutral-bg: #232d42;
  --color-tint-warning-bg: #3a2c12;
  --color-super-admin-bg: #2a3b66;
  --color-super-admin-text: #b7c6ef;

  --color-connector-type1: #fb923c;
  --color-connector-type2: #fb7191;
  --color-connector-ccs1: #a78bfa;
  --color-connector-ccs2: #38bdf8;
  --color-connector-chademo: #2dd4bf;
  --color-connector-gbt-ac: #a3e635;
  --color-connector-gbt-dc: #e879f9;
  --color-connector-tesla: #d6d3d1;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  height: 100%;
  /* `overflow: hidden` aquí — el scroll de la vista pasa a vivir en
     `main#view-root` (ver más abajo), no en el documento, para que la barra
     de desplazamiento nazca debajo de `.topbar` y ésta se vea de borde a
     borde en vez de perder su ancho de scrollbar. */
  overflow: hidden;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-ui);
  font-size: 14px;
  line-height: 1.5;
}

/* Transición sutil de color al conmutar de tema — nunca de layout/tamaño
   (sólo background/border/color), y desactivada del todo si el usuario pidió
   menos movimiento. Acotada a los elementos que de verdad cambian de tono
   entre temas (evita "transition: all", que animaría cosas que no deberían,
   como el ancho de un tooltip). */
body, .card, .stat-tile, .btn, .topbar .user-box > button, .user-menu-trigger,
.user-menu-dropdown, .user-menu-item, table, thead th, tbody td, .banner,
.pill, input:not([type=checkbox]):not([type=radio]):not([type=file]), select, textarea,
.connector-card, .login-card, .switch-row, .combobox-menu, .log-nature-chip {
  transition: background-color 0.18s ease, border-color 0.18s ease, color 0.18s ease;
}
@media (prefers-reduced-motion: reduce) {
  body, .card, .stat-tile, .btn, .topbar .user-box > button, .user-menu-trigger,
  .user-menu-dropdown, .user-menu-item, table, thead th, tbody td, .banner,
  .pill, input:not([type=checkbox]):not([type=radio]):not([type=file]), select, textarea,
  .connector-card, .login-card, .switch-row, .combobox-menu, .log-nature-chip {
    transition: none;
  }
}

button, input, select, textarea { font-family: inherit; font-size: inherit; }

code, .mono, .js-json, table td.num, table td.id, .timestamp {
  font-family: var(--font-mono);
}

a { color: var(--color-heading); }

/* ---------------------------------------------------------------- shell */

/* `height: 100vh` (antes `min-height`) — ahora que `html, body` no hacen
   scroll, el shell debe ocupar exactamente el viewport para que `main`
   pueda repartirse el resto con `flex: 1` y desplazarse por su cuenta. */
#app-shell { display: flex; flex-direction: column; height: 100vh; }

.topbar {
  background: var(--color-navy);
  color: var(--color-text-inverse);
  display: flex;
  align-items: center;
  padding: 0 20px;
  /* 64px (antes 56px): el logo de INPEL exige mínimo 90px de ancho, que a la
     proporción real del SVG (811.9 x 516.6) equivale a ~57.3px de alto —
     ligeramente MÁS que los 56px originales de la barra. Se agranda la
     barra entera (no sólo el logo) para que quede con aire arriba/abajo en
     vez de tocar los bordes o desbordar. */
  height: 64px;
  flex-shrink: 0;
}

/* Logo del propio producto — primero del todo en la barra superior.
   Reemplaza el texto "NEO" + subtítulo "Laboratorio OCPP 1.6J" que llevaba
   el `.topbar .brand` (clase ahora sin uso, retirada). Alto fijo en 57.3px
   — el mismo alto YA renderizado por el logo de INPEL (90px de ancho a su
   proporción real, ver `.topbar-inpel-logo`) — con `width:auto` para
   conservar la proporción real de ESTE archivo; a ese alto da ~104.7px de
   ancho, por encima del mínimo de 100px ya pedido antes, así que ambas
   condiciones quedan satisfechas a la vez. El espacio a la izquierda de
   este logo (hasta el borde de la barra) ya es igual al de la derecha del
   logo de INPEL sin tocar nada más: ambos son sólo el padding lateral del
   propio `.topbar` (20px), ninguno de los dos logos tiene margen extra en
   ese lado. */
.topbar-neo-logo { height: 57.3px; width: auto; margin-right: 32px; flex-shrink: 0; }

/* Logo institucional de INPEL S.A. (propietario, ver "Acerca de neo") — en
   el extremo derecho de la barra, después del menú de usuario (es el ÚLTIMO
   hijo de `.topbar`, que ya empuja todo lo anterior con `nav { flex:1 }`).
   Colores del SVG (rosa corporativo) fijos en el propio archivo: no
   necesita adaptarse entre tema claro/oscuro, igual que el resto de la
   identidad de marca sobre el `.topbar` (que tampoco cambia con el tema).
   Ancho fijo en 90px (el mínimo pedido) con `height:auto` para conservar la
   proporción real del archivo — nunca un alto fijo que lo deformaría. */
.topbar-inpel-logo { width: 90px; min-width: 90px; height: auto; margin-left: 20px; flex-shrink: 0; }

.topbar nav { display: flex; gap: 4px; flex: 1; }
.topbar nav button {
  background: transparent;
  border: none;
  color: #c7cde3;
  padding: 8px 14px;
  border-radius: var(--radius);
  cursor: pointer;
  font-weight: 600;
  letter-spacing: 0.03em;
  font-size: 13px;
}
.topbar nav button:hover { background: rgba(255,255,255,0.08); color: #fff; }
.topbar nav button.active { background: rgba(255,255,255,0.14); color: #fff; }
/* Redefinición "Flujo de validación de usuarios por administrador...":
   badge de usuarios pendientes sobre el ítem "Usuarios" del menú — el
   `.topbar` no cambia con el tema (siempre navy), así que un rojo fijo con
   texto blanco (mismo rojo de "necesita atención" que `.badge-in-use`)
   funciona igual en claro y oscuro sin tokens nuevos.
   `display` sólo va en `:not([hidden])` — puesto incondicionalmente en
   `.nav-badge`, un estilo de autor con esa especificidad gana siempre sobre
   el `display: none` por defecto de `[hidden]` en la hoja de estilos del
   navegador (los estilos de autor tienen prioridad sobre los de UA sin
   importar la especificidad — mismo criterio ya aplicado en
   `.user-menu-dropdown:not([hidden])`), dejando ver un punto rojo vacío
   incluso con el conteo en 0. */
.nav-badge:not([hidden]) {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 16px; height: 16px; padding: 0 4px; margin-left: 6px;
  border-radius: 999px; background: var(--color-faulted); color: #fff;
  font-size: 10px; font-weight: 700; line-height: 1; vertical-align: 2px;
}

.topbar .user-box { display: flex; align-items: center; gap: 10px; font-size: 12.5px; color: #c7cde3; margin-left: auto; }
.topbar .user-box > button { background: transparent; border: 1px solid rgba(255,255,255,0.25); color: #fff; padding: 5px 10px; border-radius: var(--radius); cursor: pointer; }
.topbar .user-box > button:hover { background: rgba(255,255,255,0.1); }

/* Redefinición "Menú desplegable de gestión de usuario": reemplaza el botón
   plano "Salir" — un único trigger (avatar + nombre) que despliega las tres
   acciones de cuenta. Primer menú de este tipo en el frontend: no hay un
   componente de dropdown previo que reutilizar, sólo el patrón visual ya
   establecido de tarjeta flotante (mismo `--shadow-md`/`--radius` que el
   resto de la consola). */
.user-menu { position: relative; }
.user-menu-trigger {
  display: flex; align-items: center; gap: 8px;
  background: transparent; border: 1px solid rgba(255,255,255,0.25); border-radius: var(--radius);
  padding: 4px 10px 4px 4px; cursor: pointer; color: #fff;
}
.user-menu-trigger:hover { background: rgba(255,255,255,0.1); }
.user-menu-trigger[aria-expanded="true"] { background: rgba(255,255,255,0.14); }
.user-menu-avatar {
  width: 26px; height: 26px; border-radius: 50%; flex-shrink: 0;
  background: var(--color-pink); color: #fff; font-weight: 700; font-size: 11px;
  display: flex; align-items: center; justify-content: center; letter-spacing: 0.01em;
  background-size: cover; background-position: center;
}
.user-menu-name { font-size: 12.5px; font-weight: 600; max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-menu-dropdown {
  position: absolute; top: calc(100% + 8px); right: 0; z-index: 950;
  background: var(--color-surface); border: 1px solid var(--color-border); border-radius: var(--radius);
  box-shadow: var(--shadow-md); min-width: 220px; padding: 6px; flex-direction: column; gap: 2px;
}
/* `display: flex` sólo cuando NO está `[hidden]` — puesto incondicionalmente
   en `.user-menu-dropdown`, un estilo de autor con esa especificidad gana
   siempre sobre el `display: none` por defecto de `[hidden]` en la hoja de
   estilos del navegador (los estilos de autor tienen prioridad sobre los de
   UA sin importar la especificidad), dejando el menú desplegado siempre. */
.user-menu-dropdown:not([hidden]) { display: flex; }
.user-menu-item {
  display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  background: transparent; border: none; border-radius: calc(var(--radius) - 2px);
  padding: 9px 10px; cursor: pointer; font-size: 13px; color: var(--color-text);
}
.user-menu-item:hover, .user-menu-item:focus-visible { background: var(--color-bg); outline: none; }
.user-menu-item.user-menu-item-danger { color: var(--color-faulted); }
.user-menu-sep { height: 1px; background: var(--color-border); margin: 4px 2px; }

/* Redefinición "Sistema de temas claro/oscuro...": selector de 3 opciones
   SIEMPRE visible dentro del propio desplegable (no un submenú flotante que
   se abre aparte) — mismas 3 alternativas alcanzables con Tab sin aprender
   un segundo patrón de interacción. */
.user-menu-theme-label { font-size: 10.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-text-muted); padding: 6px 10px 2px; }
.user-menu-theme-options { display: flex; gap: 4px; padding: 2px 8px 6px; }
.user-menu-theme-option {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: transparent; border: 1px solid var(--color-border); border-radius: calc(var(--radius) - 1px);
  padding: 7px 4px; cursor: pointer; font-size: 10.5px; font-weight: 600; color: var(--color-text-muted);
}
.user-menu-theme-option:hover, .user-menu-theme-option:focus-visible { background: var(--color-bg); outline: none; }
.user-menu-theme-option[aria-checked="true"] { border-color: var(--color-pink); color: var(--color-pink); background: var(--color-pink-soft); }
.user-menu-theme-option .icon { width: 15px; height: 15px; }

@media (max-width: 720px) {
  .user-menu-name { display: none; }
  #user-menu-role { display: none; }
}

/* `overflow-y: auto` + `min-height: 0`: convierte a `main` en el único
   contenedor con scroll de la aplicación (ver `html, body` arriba) — el
   `min-height: 0` es necesario porque, sin él, un hijo `flex: 1` nunca se
   encoge por debajo de su contenido y el scroll interno no llegaría a
   activarse. */
main#view-root { flex: 1; min-height: 0; overflow-y: auto; padding: 20px 24px 40px; max-width: 1400px; width: 100%; margin: 0 auto; }

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

.card {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 16px 18px;
  margin-bottom: 16px;
}
.card h2, .card h3 { margin-top: 0; }

.card-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
/* Redefinición "Balance energético global de la plataforma": misma técnica
   auto-fit/minmax que `.card-grid`, con un mínimo mayor porque cada tarjeta
   trae su propia gráfica — 3 columnas en desktop, 2 en tablet, 1 en móvil,
   sin escribir ningún breakpoint nuevo a mano. */
.card-grid-lg { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }

.stat-tile {
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  padding: 14px 16px;
}
.stat-tile .value { font-size: 26px; font-weight: 700; color: var(--color-heading); font-family: var(--font-mono); }
.stat-tile .label { font-size: 12px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.04em; }

h1 { font-size: 19px; margin: 0 0 14px; color: var(--color-heading); }
h2 { font-size: 15px; color: var(--color-heading); }
h3 { font-size: 13px; color: var(--color-heading); text-transform: uppercase; letter-spacing: 0.03em; }

.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; }

/* ---------------------------------------------------------------- tabla */

table { width: 100%; border-collapse: collapse; font-size: 13px; }
thead th {
  text-align: left;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
  border-bottom: 2px solid var(--color-border);
  padding: 8px 10px;
  position: sticky;
  top: 0;
  background: var(--color-surface);
}
tbody td { padding: 8px 10px; border-bottom: 1px solid var(--color-border); vertical-align: middle; }
tbody tr:hover { background: var(--color-surface-2); }
tbody tr.clickable { cursor: pointer; }
.table-scroll { overflow-x: auto; max-height: 70vh; overflow-y: auto; }

/* Corrección "Encabezado fijo de las tablas de variables OCPP configurables"
   (MeterValues / StopTransaction): el `thead th` sticky de arriba ya es
   opaco y queda por encima del contenido en flujo normal, pero `.switch`
   (dentro de cada celda del cuerpo) es `position: relative` — un elemento
   POSICIONADO igual que el `th` sticky. Entre dos elementos posicionados con
   `z-index: auto`, gana el que aparece DESPUÉS en el DOM, y `<tbody>` va
   siempre después de `<thead>`: sin un `z-index` explícito aquí, los
   switches de las filas terminan pintándose por ENCIMA del encabezado fijo
   al hacer scroll, en vez de deslizarse por debajo. */
.measurand-config-table thead th { z-index: 2; }

/* Resaltador del mensaje OCPP que se está viendo en el panel "Payload JSON"
   (sección Logs) — identificado por `ocpp_message_id`, no por posición, para
   que sobreviva a los refrescos en vivo del listado (sección "streaming
   bidireccional de mensajes"). Mismo acento rosa que ya usan los estados
   "activos" del resto de la interfaz (tabs, botones primarios). */
tbody tr.row-selected,
tbody tr.row-selected:hover { background: var(--color-pink-soft); box-shadow: inset 3px 0 0 var(--color-pink); }

/* ------------------------------------------------------------- botones */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-text);
  padding: 7px 13px;
  border-radius: var(--radius);
  cursor: pointer;
  font-weight: 600;
  font-size: 13px;
  transition: background 0.12s ease, border-color 0.12s ease, transform 0.06s ease;
}
.btn:hover { border-color: var(--color-border); background: var(--color-surface-2); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }

.btn-primary { background: var(--color-pink); border-color: var(--color-pink); color: #fff; }
.btn-primary:hover { background: #e01749; border-color: #e01749; }

.btn-danger { border-color: var(--color-faulted); color: var(--color-faulted); background: var(--color-surface); }
.btn-danger:hover { background: var(--color-alert-bg); }

/* Redefinición "Botón de Parada de Emergencia a nivel de cargador": única
   acción de todo el laboratorio que pide un tamaño/color deliberadamente
   más destacado que el resto de los "destructivos" (`.btn-danger`, sólo
   contorno) — mismo token de color que siempre (`--color-faulted`, nunca
   uno nuevo), en relleno sólido y con más padding/tamaño de letra.
   `filter: brightness()` en vez de un hex fijo en `:hover` porque
   `--color-faulted` cambia de valor entre tema claro/oscuro. */
.btn-emergency {
  background: var(--color-faulted); border-color: var(--color-faulted); color: #fff;
  padding: 9px 16px; font-size: 14px; font-weight: 700;
}
.btn-emergency:hover { filter: brightness(0.88); }

/* Redefinición "Estado visual y funcional del botón de parada de
   emergencia tras su accionamiento": parpadeo puramente CSS (@keyframes),
   nunca JS. No existe un token "naranja" puro en la paleta corporativa —
   se reutiliza `--color-preparing` (el ámbar ya usado para alertas de
   estado) alternado con `--color-text-inverse` (el único "blanco" que se
   mantiene igual entre temas claro/oscuro — a diferencia de
   `--color-surface`, que en modo oscuro es navy, no blanco). El texto
   también alterna para mantener contraste legible en ambas fases; los
   saltos 49.9%/50% (en vez de una única mitad) fuerzan un corte instantáneo
   — un parpadeo real, no un cross-fade suave. */
@keyframes emergency-blink {
  0%, 49.9% { background-color: var(--color-preparing); border-color: var(--color-preparing); color: #fff; }
  50%, 100% { background-color: var(--color-text-inverse); border-color: var(--color-preparing); color: var(--color-preparing); }
}
.btn-emergency-active { animation: emergency-blink 1s linear infinite; }
@media (prefers-reduced-motion: reduce) {
  .btn-emergency-active {
    animation: none; background: var(--color-preparing); border-color: var(--color-preparing); color: #fff;
  }
}

.btn-ghost { border-color: transparent; background: transparent; }
.btn-ghost:hover { background: var(--color-surface-2); }

.btn-sm { padding: 4px 9px; font-size: 12px; }

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

label { display: block; font-size: 12.5px; font-weight: 600; color: var(--color-text-muted); margin-bottom: 4px; }
/* Selector por EXCLUSIÓN, no por lista de `type` — un <input> sin atributo
   `type` (text por defecto del navegador) no calzaba con la lista anterior
   basada en `input[type=...]` y se quedaba con el estilo nativo del
   navegador: de ahí la altura/borde distintos entre esos campos y el resto
   (sección "Comunicación OCPP"). checkbox/radio/file quedan afuera porque
   tienen su propio estilo (.switch, .radio-list, input[type=file]). */
input:not([type=checkbox]):not([type=radio]):not([type=file]), select, textarea {
  width: 100%;
  height: 38px;
  padding: 8px 10px;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-text);
  margin-bottom: 12px;
  font-size: 14px;
  font-family: inherit;
}
textarea { height: auto; }
input:focus, select:focus, textarea:focus { outline: 2px solid var(--color-pink); outline-offset: 1px; border-color: var(--color-pink); }
.field-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 0 14px; }
/* Fila Esquema+Host / Puerto+Path de "Comunicación OCPP": ancho proporcional
   al largo esperado del contenido — Esquema ("wss://") y Puerto (hasta 5
   dígitos) son cortos y no deben ocupar lo mismo que Host o Path. */
.field-row-narrow-wide { display: grid; grid-template-columns: minmax(90px, 200px) minmax(150px, 420px); gap: 0 14px; }
/* A diferencia de `.field-row` (auto-fit: ya colapsa solo a 1 columna si no
   hay espacio), esta usa 2 columnas FIJAS — sin este respaldo, en modales
   angostos o pantallas pequeñas los campos quedan amontonados en vez de
   apilarse en una sola columna legible. */
@media (max-width: 460px) {
  .field-row-narrow-wide { grid-template-columns: 1fr; }
}
fieldset { border: 1px solid var(--color-border); border-radius: var(--radius); margin: 0 0 16px; padding: 14px 16px; }
legend { padding: 0 6px; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: var(--color-heading); }
.form-help { font-size: 11.5px; color: var(--color-text-muted); margin-top: -8px; margin-bottom: 12px; }

/* ------------------------------------------------------------- estados */

.state-badge { display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size: 12.5px; }
.state-dot { width: 9px; height: 9px; border-radius: 50%; flex-shrink: 0; display: inline-block; }
.state-available .state-dot { background: var(--color-available); }
.state-preparing .state-dot { background: var(--color-preparing); }
/* Redefinición "Colores de estado y secuencia de carga": Charging (fijo),
   SuspendedEV/SuspendedEVSE y Finishing (parpadeantes, ver .state-blink)
   comparten el mismo azul — sólo la animación los distingue entre sí. */
.state-charging .state-dot,
.state-suspendedev .state-dot,
.state-suspendedevse .state-dot,
.state-finishing .state-dot { background: var(--color-finishing); }
.state-reserved .state-dot { background: var(--color-reserved); }
.state-faulted .state-dot { background: var(--color-faulted); }
.state-offline .state-dot { background: var(--color-offline); }

/* Redefinición "Indisponibilidad de conectores DC y comportamiento del
   LED": dos causas distintas de `Unavailable` exigen dos LEDs distintos,
   aunque el estado OCPP sea el mismo — la clase la decide el frontend según
   `Connector.unavailable_reason` (ver `connectorLedClass` en app.js), nunca
   sólo el string de estado OCPP, que es igual en ambos casos. */
.state-unavailable-module-busy .state-dot { background: var(--color-finishing); }
.state-unavailable-admin .state-dot { background: transparent; border: 1.5px solid var(--color-text-muted); }

/* Redefinición "Colores de estado y secuencia de carga": SuspendedEV y
   Finishing son el único azul que parpadea — Charging usa el mismo azul
   fijo, sin esta clase. */
@keyframes state-blink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.2; }
}
.state-badge.state-blink .state-dot { animation: state-blink 1s ease-in-out infinite; }

.pill { display: inline-block; padding: 2px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: 0.02em; }
.pill-online { background: var(--color-tint-success-bg); color: var(--color-available); }
.pill-offline { background: var(--color-tint-neutral-bg); color: var(--color-offline); }
/* Redefinición "Sistema de roles de usuario con permisos diferenciados para
   operaciones de eliminación": mismo componente `.pill` ya existente
   (online/offline) — sólo se agregan los dos colores que faltaban. */
.pill-admin { background: var(--color-pink-soft); color: var(--color-pink); }
.pill-user { background: var(--color-tint-neutral-bg); color: var(--color-text-muted); }
/* Redefinición "Convierte a un usuario en Super Admin...": tinte + texto
   PROPIOS que sí se adaptan por tema (ver `--color-super-admin-bg/-text`
   más arriba) — el navy sólido usado antes casi desaparecía en tema
   oscuro contra `--color-surface`, una superficie igual de oscura. */
.pill-super-admin { background: var(--color-super-admin-bg); color: var(--color-super-admin-text); }
/* Redefinición "Flujo de validación de usuarios por administrador...":
   estado de cuenta — "Activo" reutiliza `.pill-online` (mismo verde ya
   usado para "en línea"), "Pendiente"/"Bloqueado" son los dos únicos
   colores de estado nuevos que aporta esta redefinición. */
.pill-pending { background: var(--color-tint-warning-bg); color: var(--color-preparing); }
.pill-blocked { background: var(--color-alert-bg); color: var(--color-faulted); }

.icon { width: 16px; height: 16px; stroke: currentColor; fill: none; stroke-width: 2; vertical-align: -3px; }

/* ------------------------------------------------------------ mensajes */

.banner { border-radius: var(--radius); padding: 10px 14px; margin-bottom: 14px; font-size: 13px; }
.banner-error { background: var(--color-alert-bg); border: 1px solid var(--color-alert-border); color: var(--color-faulted); }
.banner-info { background: var(--color-info-bg); border: 1px solid var(--color-info-border); color: var(--color-info-text); }
.banner-pending { background: var(--color-alert-bg); border: 1px dashed var(--color-pending-border); color: var(--color-pending-text); font-size: 12.5px; }

.empty-state { text-align: center; padding: 40px 20px; color: var(--color-text-muted); }

.json-view {
  background: #0e1d3c;
  color: #dfe6ff;
  padding: 12px 14px;
  border-radius: var(--radius);
  font-size: 12px;
  overflow: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

.split { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; align-items: start; }
@media (max-width: 900px) { .split { grid-template-columns: 1fr; } }
/* Mensajes OCPP (Logs): el card de la tabla (izquierda, sin título, padding
   0) y el del payload (derecha, con "Payload JSON" + padding normal) tienen
   "chrome" distinto — igualar sólo el alto del área con scroll deja los
   CONTENEDORES visibles (el marco completo) de alto distinto, porque a la
   derecha ese marco además carga el título y el padding. Por eso el alto
   fijo de 70vh va en el CARD mismo (no en el hijo con scroll): ambos marcos
   quedan idénticos siempre, y el hijo con scroll ocupa como flex el espacio
   que le sobra dentro de ese card ya fijo. Acotado a esta instancia de
   `.split` en particular (`.ocpp-messages-split`) — el `.split` de Resumen
   usa cards de alto natural (tiles), y forzarlos a 70vh los estiraría con
   espacio vacío de sobra. */
.ocpp-messages-split > .card { display: flex; flex-direction: column; height: 70vh; }
.ocpp-messages-split > .card > .table-scroll,
.ocpp-messages-split > .card > .json-view {
  flex: 1;
  min-height: 0;
  max-height: none;
}

.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--color-border); margin-bottom: 16px; }
.tabs button { background: none; border: none; padding: 9px 14px; cursor: pointer; font-weight: 600; font-size: 13px; color: var(--color-text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tabs button.active { color: var(--color-heading); border-bottom-color: var(--color-pink); }

.connector-card { border: 1px solid var(--color-border); border-radius: var(--radius); padding: 14px; background: var(--color-surface); }
.connector-card .metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin: 10px 0; }
.connector-card .metrics div { text-align: center; }
.connector-card .metrics .v { font-family: var(--font-mono); font-weight: 700; font-size: 15px; color: var(--color-heading); }
.connector-card .metrics .l { font-size: 10.5px; color: var(--color-text-muted); text-transform: uppercase; }
.connector-card .actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; margin-top: 10px; }

.login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: var(--color-navy); padding: 20px; }
.login-card { background: var(--color-surface); padding: 32px 30px; border-radius: 10px; width: 100%; max-width: 420px; box-shadow: var(--shadow-md); }
.login-card h1 { text-align: center; }

/* Redefinición "Rediseño de login + OAuth + recuperación de contraseña". */
.login-logo { display: block; height: 88px; width: auto; max-width: 100%; margin: 0 auto 14px; }
.login-title { margin: 0 0 4px; text-align: center; }
.login-subtitle { text-align: center; color: var(--color-text-muted); font-size: 13px; margin-bottom: 22px; }

/* El ícono queda dentro del propio cuadro (layout flex: ícono, campo,
   [botón mostrar/ocultar]) en vez de superpuesto por posición absoluta
   sobre el input — el selector genérico de inputs
   (`input:not([type=checkbox])...`, más específico que `.input-with-icon
   input` por sus tres `:not()`) igual gana la pelea de especificidad sobre
   cualquier padding que se le intente fijar al `<input>` directamente, así
   que aquí el borde/fondo/alto viven en el CONTENEDOR y el input interno
   se neutraliza explícitamente (de ahí los `!important`, acotados a esta
   variante) — nunca vuelve a superponerse con el texto escrito. */
.input-with-icon {
  display: flex; align-items: center; gap: 8px;
  height: 38px; padding: 0 10px; margin-bottom: 12px;
  border: 1px solid var(--color-border); border-radius: var(--radius);
  background: var(--color-surface);
}
.input-with-icon:focus-within { outline: 2px solid var(--color-pink); outline-offset: 1px; border-color: var(--color-pink); }
.input-with-icon .icon { flex-shrink: 0; color: var(--color-text-muted); pointer-events: none; }
.input-with-icon input {
  flex: 1; min-width: 0;
  border: none !important; background: transparent !important; padding: 0 !important;
  margin-bottom: 0 !important; height: auto !important;
}
.input-with-icon input:focus { outline: none !important; border: none !important; }
.password-toggle {
  flex-shrink: 0; background: transparent; border: none; padding: 4px; cursor: pointer; color: var(--color-text-muted);
  display: flex; align-items: center; justify-content: center; min-width: 28px; min-height: 28px; margin-right: -4px;
}
.password-toggle:hover, .password-toggle:focus-visible { color: var(--color-heading); outline: none; }

.login-row-between { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin: -2px 0 14px; flex-wrap: wrap; }
.remember-row { display: flex; align-items: center; gap: 6px; font-size: 12.5px; font-weight: 500; color: var(--color-text); margin: 0; }
.remember-row input { margin: 0; }
.login-link-sm { font-size: 12.5px; font-weight: 600; }

.login-footer-link { text-align: center; margin-top: 18px; font-size: 12.5px; color: var(--color-text-muted); }

.btn-spinner {
  display: inline-block; width: 13px; height: 13px; border-radius: 50%;
  border: 2px solid rgba(255, 255, 255, 0.4); border-top-color: #fff;
  animation: btn-spin 0.6s linear infinite; vertical-align: -2px; margin-right: 2px;
}
@keyframes btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .btn-spinner { animation: none; } }

.toast-container { position: fixed; top: 16px; right: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 1000; }
.toast { background: var(--color-navy); color: #fff; padding: 10px 14px; border-radius: var(--radius); box-shadow: var(--shadow-md); font-size: 13px; max-width: 340px; animation: toast-in 0.18s ease; }
.toast.error { background: var(--color-faulted); }
@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: translateY(0); } }

.conn-dot { display: inline-flex; align-items: center; gap: 5px; font-size: 12px; font-weight: 600; }
.conn-dot .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--color-offline); }
.conn-dot.connected .dot { background: var(--color-available); }

.filters { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 14px; align-items: flex-end; }
.filters .field { min-width: 150px; }
.filters label { margin-bottom: 3px; }
/* Especificidad: la regla general de `input:not(...):not(...):not(...)`
   (línea ~210) pesa más que `.filters input` a secas, así que ganaba ella
   y el input de Fabricante quedaba con margin-bottom:12px mientras los
   <select> de al lado (que sí calzan con el `select` de esa regla general,
   de menor especificidad) quedaban en 0 — de ahí el desalineamiento
   vertical entre Fabricante y Estado OCPP/Modo. Se repite aquí el mismo
   :not() para igualar/superar esa especificidad en vez de depender del
   orden de las reglas. */
.filters input:not([type=checkbox]):not([type=radio]):not([type=file]), .filters select { margin-bottom: 0; }
/* Mismo bug, otra forma: `.input-with-icon` (usada por el campo "Buscar" de
   Usuarios) es un <div>, no un <input>/<select> — ninguna de las dos reglas
   de arriba lo alcanza, así que conservaba su propio `margin-bottom: 12px`
   (ver esa clase) mientras los `<select>` vecinos ya estaban en 0. Con
   `align-items: flex-end` en `.filters`, ese margen extra hace que el
   cuadro de búsqueda se vea más arriba que "Estado"/"Rol" aunque estén en
   la misma fila — el mismo desalineamiento vertical descrito arriba, sólo
   que por un contenedor distinto al de aquel caso. */
.filters .input-with-icon { margin-bottom: 0; }

/* --------------------------------------------------- switch (configuración) */
/* Flex-wrap de chips de ancho fijo (ver `.switch-row`): cuántos entran por
   fila depende del ancho disponible. `justify-content: center` centra cada
   fila dentro del contenedor — incluida la última si tiene menos chips que
   las demás, aunque eso le haga perder la alineación de columnas con el
   resto: se prioriza no dejar todo el espacio sobrante de un solo lado. */
.switch-grid { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 14px; margin: 10px 0; }
/* El recuadro propio (fondo + borde) es lo que evita la ambigüedad de "¿de
   quién es este switch?": cada opción queda encerrada como una unidad
   visual. Ancho FIJO (no basado en el contenido propio de cada fila): el
   texto ON/OFF cambia de largo al togglear (p. ej. "Total"/"Parcial",
   "Big"/"Little"), así que un ancho intrínseco haría que el chip cambiara
   de tamaño al cambiar el switch. El ancho se fija al del chip más grande
   (etiqueta + texto ON/OFF más largos de toda la lista) para que todos
   midan lo mismo y ninguno se mueva al togglear. `justify-content:
   space-between` deja el switch siempre pegado al borde derecho del chip,
   sin importar cuánto mida la etiqueta o el texto ON/OFF. */
.switch-row { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 8px 10px; background: var(--color-bg); border: 1px solid var(--color-border); border-radius: var(--radius); flex: 0 1 300px; max-width: 300px; }
/* overflow-wrap:anywhere — sin esto, una etiqueta sin espacios (una sola
   palabra larga) no tiene dónde partirse y desborda el ancho fijo de 300px
   del chip en vez de ajustarse. */
.switch-row .switch-label { font-size: 13px; color: var(--color-text); overflow-wrap: anywhere; }
/* Agrupa el texto ON/OFF y el switch para que se muevan juntos como una
   sola unidad pegada al borde derecho (space-between de `.switch-row` sólo
   ve dos hijos: la etiqueta y este grupo). El texto ON/OFF tiene ancho fijo
   y alineado a la derecha para que "Parcial"/"Total"/"SI"/"NO" queden en la
   misma columna vertical en todas las filas, sin importar su propio largo. */
.switch-control { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.switch-row .switch-on-off { font-size: 11px; font-weight: 600; color: var(--color-text-muted); white-space: nowrap; width: 46px; text-align: right; }
.switch { position: relative; display: inline-block; width: 40px; height: 22px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch .slider { position: absolute; inset: 0; background: var(--color-border); border-radius: 999px; cursor: pointer; transition: background 0.15s; }
.switch .slider::before { content: ""; position: absolute; width: 16px; height: 16px; left: 3px; top: 3px; background: #fff; border-radius: 50%; transition: transform 0.15s; box-shadow: 0 1px 2px rgba(0,0,0,0.25); }
/* Redefinición "Sistema de temas claro/oscuro...": era `var(--color-navy)`
   — navy no cambia con el tema (ancla el `.topbar`), así que en tema oscuro
   el riel "encendido" quedaba casi del mismo tono que el fondo oscuro de la
   tarjeta, indistinguible del riel "apagado" (`--color-border`). El rosa
   corporativo, en cambio, ya es vívido en ambos temas sin ajuste — y es el
   mismo acento que el resto de la interfaz usa para "activo/seleccionado". */
.switch input:checked + .slider { background: var(--color-pink); }
.switch input:checked + .slider::before { transform: translateX(18px); }
.switch input:disabled + .slider { opacity: 0.5; cursor: not-allowed; }

/* Selector de dos opciones igualmente válidas (Parcial/Total, Little/Big):
   nunca debe leerse como apagado/encendido — ambas posiciones usan un color
   principal, sólo cambia CUÁL de los dos. Especificidad deliberadamente MÁS
   ALTA que las reglas base (.switch + .switch-selector, no sólo
   .switch-selector) para que gane sin depender del orden de aparición.
   `--color-heading` (no `--color-navy` directo) por el mismo motivo de
   arriba — necesita adaptarse en tema oscuro para seguir contrastando. */
.switch.switch-selector .slider { background: var(--color-heading); }
.switch.switch-selector input:checked + .slider { background: var(--color-pink); }

/* --------------------------------------------------------- sesión / gráfica */

.session-chart-card canvas { width: 100%; height: 280px; display: block; touch-action: none; }

/* Redefinición "Gráficas de sesiones de carga": barra de Zoom In/Out/Vista
   completa, en su propia fila justo encima del canvas — nunca superpuesta
   sobre la gráfica, para no tapar datos. */
.chart-toolbar { display: flex; justify-content: flex-end; gap: 6px; margin-bottom: 6px; }
.chart-toolbar .btn { padding: 4px 8px; }
.chart-toolbar .btn svg { width: 15px; height: 15px; }

.session-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; margin-bottom: 16px; }
.session-header .field { min-width: 140px; }
.session-header .field .l { font-size: 11px; text-transform: uppercase; color: var(--color-text-muted); letter-spacing: 0.03em; }
.session-header .field .v { font-family: var(--font-mono); font-size: 14px; color: var(--color-heading); font-weight: 600; }

/* ------------------------------------- resumen "Configuración vigente" OCPP */
/* Una fila por NATURALEZA de valor (segundos / Parcial-Total-Big-Little /
   SI-NO), no por orden de declaración — ver `ocppConfigSummaryHtml`. Cada
   fila es su propio flex-wrap centrado: si el grupo SI/NO ocupa más de una
   línea, cada línea se centra de forma independiente, con el mismo espacio
   sobrante a ambos lados aunque las demás filas tengan otra cantidad de
   columnas. */
.ocpp-config-groups { display: flex; flex-direction: column; gap: 14px; }
.ocpp-config-row { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 28px; padding-top: 14px; border-top: 1px solid var(--color-border); }
.ocpp-config-row:first-child { padding-top: 0; border-top: none; }
.ocpp-config-row .field { min-width: 140px; text-align: center; }
.ocpp-config-row .field .l { font-size: 11px; text-transform: uppercase; color: var(--color-text-muted); letter-spacing: 0.03em; }
.ocpp-config-row .field .v { font-family: var(--font-mono); font-size: 14px; color: var(--color-heading); font-weight: 600; }

.radio-list { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.radio-list label { display: flex; align-items: center; gap: 8px; font-weight: 500; color: var(--color-text); cursor: pointer; }
.radio-list input[type=radio] { width: auto; margin: 0; }

.badge-in-use { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 10.5px; font-weight: 700; background: var(--color-alert-bg); color: var(--color-faulted); }

.breadcrumb { font-size: 12.5px; color: var(--color-text-muted); margin-bottom: 10px; }
.breadcrumb button { background: none; border: none; color: var(--color-heading); cursor: pointer; font-weight: 600; padding: 0; font-size: 12.5px; }
.breadcrumb button:hover { text-decoration: underline; }

.power-preview, .endpoint-preview { font-family: var(--font-mono); font-weight: 700; color: var(--color-heading); background: var(--color-bg); border: 1px dashed var(--color-border); border-radius: var(--radius); padding: 8px 10px; margin-bottom: 12px; word-break: break-all; }
/* Vista previa con varias magnitudes: cada una en su propia línea,
   centrada dentro del contenedor (p.ej. potencia aparente/activa, o
   potencia DC de entrada/entregable — redefinición "Potencia del cargador
   desde tensión/corriente/FP"). */
.power-preview.power-preview-lines { display: flex; flex-direction: column; align-items: center; gap: 2px; text-align: center; }

/* -------------------------------------------- combobox (marca/modelo) */
/* Reemplaza <input list> + <datalist>: el desplegable nativo de datalist lo
   dibuja el sistema operativo y no admite CSS (fondo gris/blanco genérico
   según el SO, nunca los colores corporativos). Este combobox propio se ve
   y se filtra igual, pero con estilos propios de la paleta del proyecto. */
.combobox { position: relative; }
.combobox-menu {
  display: none;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  background: var(--color-surface);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  max-height: 220px;
  overflow-y: auto;
  z-index: 50;
}
.combobox-menu.open { display: block; }
.combobox-option { padding: 8px 10px; font-size: 13.5px; color: var(--color-text); cursor: pointer; }
.combobox-option:hover, .combobox-option.active { background: var(--color-navy); color: var(--color-text-inverse); }
.combobox-empty { padding: 8px 10px; font-size: 12.5px; color: var(--color-text-muted); }

/* --------------------------------------- Botón "Log": visor general del cargador */
.log-viewer-card { width: calc(100vw - 40px); max-width: 1400px; max-height: 92vh; overflow: auto; display: flex; flex-direction: column; }
.log-retention-note { font-size: 11.5px; color: var(--color-text-muted); margin: -6px 0 10px; }

.log-nature-chips { display: flex; gap: 6px; flex-wrap: wrap; }
.log-nature-chip {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px 4px 8px;
  border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: 0.01em;
  border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text-muted); cursor: pointer;
}
/* Redefinición "visor de log": el check ya no se muestra — el cambio de
   color de fondo (`.active`, ver reglas de abajo) basta para identificar
   qué naturalezas están filtrando. Oculto visualmente, no con
   `display:none`: sigue alcanzable con Tab y reacciona a Espacio como un
   checkbox real (la etiqueta que lo envuelve ya delega el clic). */
.log-nature-chip input {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.log-nature-chip:focus-within { outline: 2px solid var(--color-pink); outline-offset: 1px; }
.log-nature-chip.active { color: #fff; border-color: transparent; }
.log-nature-chip[data-nature="OCPP_MSG"].active { background: #3457a6; }
.log-nature-chip[data-nature="STATE_CHANGE"].active { background: var(--color-finishing); }
.log-nature-chip[data-nature="ENERGY"].active { background: var(--color-pink); }
.log-nature-chip[data-nature="AUTH"].active { background: #8b5cf6; }
.log-nature-chip[data-nature="NETWORK"].active { background: var(--color-preparing); }
.log-nature-chip[data-nature="ERROR"].active { background: var(--color-faulted); }
.log-nature-chip[data-nature="SYSTEM"].active { background: var(--color-offline); }
.log-nature-chip[data-nature="VEHICLE"].active { background: var(--color-available); }
/* Redefinición "Ampliación del Log General: eventos TCP/red y auditoría de
   cambios" — 2 naturalezas nuevas, mismo patrón que las 8 de arriba. */
.log-nature-chip[data-nature="TCP"].active { background: var(--color-nature-tcp); }
.log-nature-chip[data-nature="AUDIT"].active { background: var(--color-nature-audit); }
/* Icono representativo (sólo TCP/AUDIT traen uno — `logNatureIcon`, en
   `icons.js`, devuelve "" para las 8 naturalezas existentes, así que esta
   regla no las afecta). El `.icon` global es 16px; en un chip/pill de 10-
   11px hace falta reducirlo para que no se vea desproporcionado. */
.log-nature-chip .icon, .log-nature-pill .icon { width: 10px; height: 10px; vertical-align: -1px; margin-right: 1px; }

.log-viewer-body-wrap { position: relative; flex: 1; min-height: 0; margin-top: 10px; }
/* Deliberadamente SIN `min-height` fijo: combinado con `max-height: 55vh`
   en una ventana baja (pantalla corta/apaisada), un mínimo mayor que ese
   55vh gana por especificación — la caja queda más alta que su propio
   `max-height` y dejar de ser scrolleable de verdad (el navegador considera
   que "todo cabe"). Sin mínimo, el visor simplemente ocupa lo que el
   contenido combinado con el 55vh permitan, sin ese conflicto. */
.log-viewer-body {
  width: 100%; max-height: 55vh; overflow-y: auto; overflow-x: hidden;
  background: var(--color-navy-soft); border-radius: var(--radius);
  padding: 6px 8px; font-family: var(--font-mono); font-size: 12px; scroll-behavior: smooth;
}
/* `flex-wrap: wrap` + una base mínima razonable en `.log-msg`: en una
   columna muy angosta (móvil), la fila de metadatos (hora/naturaleza/nivel)
   no debe exprimir el mensaje a un ancho casi nulo — que lo obligaría a
   "envolver" letra por letra en una columna altísima. Si no cabe junto a
   los metadatos, el mensaje pasa a su propia línea a ancho completo. */
.log-line { display: flex; flex-wrap: wrap; gap: 4px 8px; padding: 3px 5px; border-radius: 4px; align-items: flex-start; }
.log-line:hover { background: rgba(255, 255, 255, 0.06); }
.log-ts { flex: 0 0 auto; color: #8b93ab; white-space: nowrap; }
.log-nature-pill { flex: 0 0 auto; padding: 1px 7px; border-radius: 999px; font-size: 10px; font-weight: 700; letter-spacing: 0.02em; color: #fff; }
.log-nature-pill.nature-OCPP_MSG { background: #3457a6; }
.log-nature-pill.nature-STATE_CHANGE { background: var(--color-finishing); }
.log-nature-pill.nature-ENERGY { background: var(--color-pink); }
.log-nature-pill.nature-AUTH { background: #8b5cf6; }
.log-nature-pill.nature-NETWORK { background: var(--color-preparing); }
.log-nature-pill.nature-ERROR { background: var(--color-faulted); }
.log-nature-pill.nature-SYSTEM { background: #6b7280; }
.log-nature-pill.nature-VEHICLE { background: var(--color-available); }
.log-nature-pill.nature-TCP { background: var(--color-nature-tcp); }
.log-nature-pill.nature-AUDIT { background: var(--color-nature-audit); }
.log-level { flex: 0 0 auto; font-weight: 700; font-size: 10.5px; text-transform: uppercase; width: 40px; }
.log-level-INFO { color: #7fb3ff; }
.log-level-WARN { color: #f5b942; }
.log-level-ERROR { color: #ff6b6b; }
.log-level-DEBUG { color: #8b93ab; }
.log-msg { flex: 1 1 220px; min-width: 0; color: #dbe1f0; white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; }

.log-autoscroll-badge {
  position: absolute; bottom: 14px; left: 50%; transform: translateX(-50%);
  display: flex; align-items: center; gap: 8px; background: var(--color-navy); color: #fff;
  padding: 6px 8px 6px 14px; border-radius: 999px; font-size: 12px; box-shadow: var(--shadow-md);
}
.log-autoscroll-badge button { background: var(--color-pink); color: #fff; border: none; border-radius: 999px; padding: 4px 10px; font-size: 11.5px; font-weight: 700; cursor: pointer; }

@media (max-width: 720px) {
  .log-filters { flex-direction: column; align-items: stretch; }
  .log-filters .field { min-width: 0; width: 100%; }
}

/* Redefinición "Balance energético global de la plataforma": exportación a
   PDF sin ninguna librería nueva — el usuario usa "Guardar como PDF" del
   propio diálogo de impresión del navegador. Primer `@media print` de la
   app: aísla `#energy-balance-section` (topbar/nav/resto del resumen/
   botones y filtros de esa misma sección quedan ocultos) y fuerza los
   tokens del tema CLARO — impresión/PDF asume fondo blanco, sin importar
   el tema activo en pantalla (por eso `!important`: debe ganarle tanto a
   `:root[data-theme="dark"]` como a `prefers-color-scheme: dark`). */
@media print {
  :root {
    --color-bg: #f7f7f9 !important;
    --color-surface: #ffffff !important;
    --color-surface-2: #fafbfe !important;
    --color-border: #e2e4ea !important;
    --color-text: #1a1f2b !important;
    --color-text-muted: #5b6172 !important;
    --color-heading: #0e1d3c !important;
    --color-chart-line: #0e1d3c !important;
  }
  body * { visibility: hidden; }
  #energy-balance-section, #energy-balance-section * { visibility: visible; }
  #energy-balance-section { position: absolute; left: 0; top: 0; width: 100%; margin: 0; padding: 16px; }
  #energy-balance-section .energy-balance-toolbar { display: none !important; }
}
