/* styles.css — SCADA Carnerillo */

/* Un solo lugar con los colores. El tema claro redefine estas variables y
   todo lo demás sale solo. El atributo data-theme lo pone un script chico en
   el <head> antes de pintar, así no hay parpadeo del tema equivocado. */
:root {
  --bg:         #0c1220;
  --surface:    #111827;
  --surface-2:  #0e1626;   /* hundido: chips de bits, marcadores sin datos */
  --hover:      #162032;
  --border:     #1e2d45;
  --text:       #e2e8f0;
  --muted:      #94a3b8;
  --ok:         #22c55e;
  --ok-bg:      #052e16;
  --danger:     #ef4444;
  --danger-bg:  #1f0a0a;
  --blink:      #2a0a0a;
  --warn:       #f59e0b;
  --warn-bg:    #2a1a02;
  --info:       #38bdf8;
  --info-bg:    #082f49;
  --idle:       #374151;
  --overlay:    rgba(17, 24, 39, 0.9);   /* paneles sobre el mapa */
  --on-accent:  #04130a;                 /* texto sobre un color fuerte */
}

:root[data-theme="light"] {
  --bg:         #f4f6fa;
  --surface:    #ffffff;
  --surface-2:  #f0f3f8;
  --hover:      #e8edf5;
  --border:     #d5dde8;
  --text:       #101828;
  --muted:      #5b6577;
  --ok:         #15803d;
  --ok-bg:      #dcfce7;
  --danger:     #b91c1c;
  --danger-bg:  #fee2e2;
  --blink:      #fecaca;
  --warn:       #b45309;
  --warn-bg:    #fef3c7;
  --info:       #0369a1;
  --info-bg:    #e0f2fe;
  --idle:       #c3cad6;
  --overlay:    rgba(255, 255, 255, 0.92);
  --on-accent:  #ffffff;
}

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

/* Cualquier regla con display pisaria el hidden del navegador. */
[hidden] { display: none !important; }

html, body { height: 100%; overflow: hidden; }

body {
  font-family: 'Segoe UI', system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
}

/* Solo una vista visible a la vez */
.view { display: none; height: 100vh; height: 100dvh; flex-direction: column; }
.view.active { display: flex; }

/* ---- Barra superior (compartida) ---- */
.topbar {
  height: 52px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  display: flex;
  align-items: center;
  padding: 0 18px;
  gap: 12px;
  flex-shrink: 0;
}
.topbar h1 {
  font-size: 16px;
  font-weight: 600;
  min-width: 0;              /* sin esto un item flex no se achica */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.topbar .back-button,
.topbar .topbar-button,
.topbar .link-status,
.topbar .data-age { white-space: nowrap; flex-shrink: 0; }
.topbar .subtitle { font-size: 12px; color: var(--muted); }
.spacer { flex: 1; }

.back-button,
.topbar-button {
  padding: 6px 14px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
.back-button:hover,
.topbar-button:hover { background: var(--hover); color: var(--text); }

/* Botón cuadrado, para el del tema: solo un glifo adentro. */
.icon-button {
  width: 32px;
  padding: 0;
  font-size: 15px;
  line-height: 1;
}

/* Enlace navegador <-> servidor. NO es la frescura del dato: ver .data-age */
.link-status {
  padding: 4px 12px;
  border-radius: 12px;
  font-size: 12px;
  background: var(--idle);
  color: var(--muted);
}
.link-status.up { background: var(--ok-bg); color: var(--ok); }

/* Frescura del dato de ESTA planta. Un WebSocket sano puede estar mostrando
   datos de hace media hora: esta etiqueta es la que lo delata. */
.data-age { font-size: 12px; color: var(--muted); }
.data-age.stale { color: var(--warn); font-weight: 600; }

/* ---- Vista 1: mapa ---- */
#map { flex: 1; }
.leaflet-container { background: var(--bg); }

.marker { width: 44px; height: 44px; cursor: pointer; position: relative; }

.marker-ring {
  position: absolute; inset: 0;
  border-radius: 50%;
  border: 2px solid var(--ok);
  animation: ping 2s ease-in-out infinite;
}
.marker-center {
  position: absolute; inset: 7px;
  border-radius: 50%;
  background: var(--ok-bg);
  border: 1px solid var(--border);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px;
}
.marker-label {
  position: absolute; bottom: -20px; left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  font-size: 11px; font-weight: 600;
  background: var(--overlay);
  padding: 1px 6px; border-radius: 3px;
  color: var(--text);
}

.marker.alarm .marker-ring   { border-color: var(--danger); animation-duration: 0.8s; }
.marker.alarm .marker-center { background: var(--danger-bg); }

/* Degradada: la planta ENTREGA agua, pero con desacuerdos (una bomba en
   manual, una que no arranca, ciclado). Ver serviceState en model.js.
   El latido es más lento que el del rojo a propósito: la urgencia se lee en la
   velocidad además del color, y así se distingue sin depender de la vista. */
.marker.degraded .marker-ring   { border-color: var(--warn); animation-duration: 2.2s; }
.marker.degraded .marker-center { background: var(--warn-bg); }

/* Sin datos frescos: ni verde ni rojo. No sabemos, y hay que decirlo. */
.marker.stale .marker-ring,
.marker.unknown .marker-ring   { border-color: var(--idle); animation: none; }
.marker.stale .marker-center,
.marker.unknown .marker-center { background: var(--surface-2); opacity: 0.75; }

@keyframes ping { 50% { transform: scale(1.2); opacity: 0.5; } }

/* La vista general: el mapa y una tarjeta por planta. Al costado en pantalla
   ancha, debajo en el celular. El color es el mismo del marcador. */
.map-body { flex: 1; display: flex; min-height: 0; }
#plant-cards {
  width: 320px; overflow-y: auto;
  padding: 16px; display: flex; flex-direction: column; gap: 12px;
  background: var(--bg); border-left: 1px solid var(--border);
}
.plant-card {
  text-align: left; font: inherit; color: var(--text); cursor: pointer;
  background: var(--surface); border: 1px solid var(--border);
  border-left: 4px solid var(--idle); border-radius: 10px;
  padding: 14px 16px; display: flex; flex-direction: column; gap: 8px;
}
.plant-card:hover { background: var(--hover); }
.plant-card.ok       { border-left-color: var(--ok); }
.plant-card.degraded { border-left-color: var(--warn); }
.plant-card.alarm    { border-left-color: var(--danger); }
.card-head { display: flex; align-items: center; gap: 8px; }
.card-dot { width: 10px; height: 10px; border-radius: 50%; background: var(--idle); flex: none; }
.plant-card.ok .card-dot       { background: var(--ok); }
.plant-card.degraded .card-dot { background: var(--warn); }
.plant-card.alarm .card-dot    { background: var(--danger); }
.card-name { font-weight: 600; font-size: 15px; flex: 1; }
.card-age { font-size: 12px; color: var(--muted); }
.card-status { font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; color: var(--muted); }
.plant-card.ok .card-status       { color: var(--ok); }
.plant-card.degraded .card-status { color: var(--warn); }
.plant-card.alarm .card-status    { color: var(--danger); }
.card-pumps { display: flex; gap: 6px; }
.card-pump {
  font-size: 12px; font-weight: 600; padding: 4px 10px; border-radius: 999px;
  background: var(--surface-2); color: var(--muted); border: 1px solid var(--border);
}
.card-pump.running { background: var(--ok-bg); color: var(--ok); border-color: var(--ok); }
.card-pump.fault   { background: var(--danger-bg); color: var(--danger); border-color: var(--danger); }
.card-summary { font-size: 13px; color: var(--muted); }
.card-summary.alarm { color: var(--danger); font-weight: 600; }

@media (max-width: 600px) {
  .map-body { flex-direction: column; }
  #map { flex: none; height: 45dvh; }
  #plant-cards { width: auto; flex: 1; border-left: none; border-top: 1px solid var(--border); }
}

/* ---- Vista 2: planta ---- */
.plant-body { flex: 1; overflow-y: auto; padding: 24px; }
.panel { max-width: 720px; margin: 0 auto; }

/* Dato viejo: se sigue mostrando el último estado conocido, pero atenuado.
   Ocultarlo sería peor; hacerlo pasar por actual, mucho peor todavía. */
.panel.stale { opacity: 0.55; filter: saturate(0.4); }
.panel.no-data { opacity: 0.45; }

.alarm-banner {
  display: none;
  border-radius: 10px;
  padding: 14px 18px;
  margin-bottom: 20px;
  font-weight: 600;
  text-align: center;
}
.alarm-banner.visible { display: block; }
.alarm-banner.critical {
  background: var(--danger-bg);
  border: 1px solid var(--danger);
  color: var(--danger);
  animation: blink 1.5s step-end infinite;
}
.alarm-banner.warning {
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  color: var(--warn);
}
@keyframes blink { 50% { background: var(--blink); } }

.section-title {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted);
  margin-bottom: 12px;
  font-weight: 600;
}

/* Bombas */
.pumps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}
.pump {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 20px;
  text-align: center;
  transition: all 0.3s;
}
.pump.running { border-color: var(--ok); background: var(--ok-bg); }
.pump.fault   { border-color: var(--danger); background: var(--danger-bg); }

.pump-icon {
  width: 56px; height: 56px;
  margin: 0 auto 12px;
  border-radius: 50%;
  background: var(--idle);
  display: flex; align-items: center; justify-content: center;
  font-size: 26px;
  transition: all 0.3s;
}
/* El glifo va sobre un círculo de color fuerte: necesita su propio color. */
.pump.running .pump-icon { background: var(--ok); color: var(--on-accent); animation: spin 2s linear infinite; }
.pump.fault   .pump-icon { background: var(--danger); color: var(--on-accent); }
@keyframes spin { to { transform: rotate(360deg); } }

.pump-name   { font-size: 15px; font-weight: 600; }
.pump-status { font-size: 12px; margin-top: 6px; text-transform: uppercase; color: var(--muted); }
.pump.running .pump-status { color: var(--ok); }
.pump.fault   .pump-status { color: var(--danger); }

/* Señales generales */
.signals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin-bottom: 28px;
}
.signal {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 18px;
  display: flex;
  align-items: center;
  gap: 14px;
}
.signal-light {
  width: 16px; height: 16px;
  border-radius: 50%;
  background: var(--ok);
  flex-shrink: 0;
}
/* El tono dice QUE es la señal; `.on`, si está activa. Los dos hacen falta:
   una alarma en false es verde (todo bien), pero una señal de estado en false
   solo está inactiva, y en verde se leería como una afirmación. */
.signal.alarm.on { border-color: var(--danger); }
.signal.alarm.on .signal-light { background: var(--danger); }

.signal.info .signal-light { background: var(--idle); }   /* inactiva: neutra */
.signal.info.on { border-color: var(--info); background: var(--info-bg); }
.signal.info.on .signal-light { background: var(--info); }

/* Sin datos: gris. No sabemos, y no es lo mismo que saber que está en cero. */
.signal.unknown .signal-light { background: var(--idle); }

.signal-name  { font-size: 14px; font-weight: 500; }
.signal-value { font-size: 12px; color: var(--muted); margin-top: 2px; }
.signal.alarm.on .signal-value { color: var(--danger); }
.signal.info.on .signal-value  { color: var(--info); }

/* Un grupo cuyos nombres salen del plano pero cuya polaridad no se confirmó
   todavía contra el LOGO! real. Se dice en pantalla, no en un comentario. */
.unverified {
  margin-left: 8px;
  padding: 2px 7px;
  border-radius: 10px;
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  color: var(--warn);
  font-size: 10px;
  letter-spacing: 0.5px;
  /* El cartel no se parte: dentro de una ficha angosta quedaba "sin" en una
     línea y "verificar" en la siguiente, y un recuadro cortado al medio se
     lee como un error de la página y no como una advertencia. Que envuelva
     el nombre de la señal, no la etiqueta. */
  white-space: nowrap;
}
/* El de cada señal va más chico que el del grupo: comparte la línea con el
   nombre, que es la información principal. */
.signal-name .unverified {
  margin-left: 6px;
  padding: 1px 5px;
  font-size: 9px;
  letter-spacing: 0.3px;
  vertical-align: 1px;
}
.group-note {
  font-size: 12px;
  color: var(--muted);
  margin: -6px 0 12px;
}

/* Tira de eventos: una línea por TRANSICIÓN, no por mensaje recibido. */
.events {
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  overflow: hidden;
}
.event {
  display: flex;
  gap: 14px;
  padding: 10px 16px;
  font-size: 13px;
  border-bottom: 1px solid var(--border);
  border-left: 3px solid var(--idle);
}
.event:last-child { border-bottom: none; }
.event-time {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}
.event.critical { border-left-color: var(--danger); }
.event.warning  { border-left-color: var(--warn); }
.event.info     { border-left-color: var(--ok); }
.event.empty    { color: var(--muted); border-left-color: transparent; justify-content: center; }

/* Hueco de registro: no es un evento de la planta, es la ausencia de
   registro. Se muestra distinto a propósito. */
.event.gap {
  border-left-color: var(--warn);
  color: var(--muted);
  font-style: italic;
  background: var(--warn-bg);
}

/* Evento deducido: el servidor no lo vio pasar, lo dedujo al volver de un
   corte. El borde cortado avisa que la hora es la de la detección. */
.event.inferred { border-left-style: dashed; }
.event-note {
  color: var(--muted);
  font-size: 11px;
  font-style: italic;
}

.events-more {
  display: block;
  width: 100%;
  margin-top: 10px;
  padding: 10px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  font-size: 13px;
  cursor: pointer;
}
.events-more:hover:not(:disabled) { background: var(--hover); color: var(--text); }
.events-more:disabled { opacity: 0.6; cursor: default; }

/* ---- Filtros del historial ----
   En una fila arriba de la lista. En el celular los controles bajan solos:
   `flex-wrap` y no una consulta de medios, que para tres controles alcanza. */
.history-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 8px;
}
.history-filter,
.history-clear {
  padding: 8px 10px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 13px;
  font-family: inherit;
}
.history-filter { flex: 1 1 auto; min-width: 140px; }
.history-clear { cursor: pointer; color: var(--muted); }
.history-clear:hover { background: var(--hover); color: var(--text); }

/* Cuántos se están mostrando. Recessivo: es un dato sobre la lista, no la
   lista. Pero tiene que estar: el paginado descartaba páginas en silencio y la
   pantalla no lo delataba. */
.history-count {
  margin: 0 0 8px;
  font-size: 12px;
  color: var(--muted);
}

/* ---- Panel de bits crudos (diagnóstico de puesta en marcha) ----
   Los 28 bits tal como los manda el ESP32, con su nombre de plano. No es
   parte del tablero: sirve para verificar el mapeo contra el LOGO! real. */
.bits-panel {
  margin-top: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 14px 18px;
}
.bits-panel summary {
  cursor: pointer;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--muted);
  font-weight: 600;
}
.bits-panel[open] summary { margin-bottom: 16px; }

.bits-title {
  font-size: 11px;
  color: var(--muted);
  margin: 14px 0 8px;
  font-weight: 600;
}
.bits {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 6px;
}
.bit {
  display: flex;
  align-items: baseline;
  gap: 7px;
  padding: 5px 9px;
  border-radius: 7px;
  border: 1px solid var(--border);
  background: var(--surface-2);
  font-size: 11px;
}
.bit-id {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  min-width: 26px;
}
.bit-tag { color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* En 1 se enciende. En 0 queda apagado pero legible: los dos estados son
   informacion, no solo el 1. */
.bit.on { border-color: var(--ok); background: var(--ok-bg); }
.bit.on .bit-id, .bit.on .bit-tag { color: var(--ok); }

/* Gris: no sabemos el bit. Distinto de saber que está en cero. */
.bit.unknown { opacity: 0.4; }

/* ---- Login ----
   Tapa todo, mapa incluido (Leaflet usa z-index de hasta 1000). */
.login {
  position: fixed;
  inset: 0;
  z-index: 2000;
  overflow-y: auto;          /* celular acostado con el teclado abierto */
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: var(--bg);
}
.login-card {
  width: 100%;
  max-width: 360px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  padding: 28px 24px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  text-align: center;
}
.login-icon { font-size: 34px; }
.login-card h1 { font-size: 18px; font-weight: 600; }
.login-subtitle { font-size: 13px; color: var(--muted); margin-bottom: 4px; }
.login-input {
  width: 100%;
  padding: 12px 14px;
  border-radius: 9px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 16px;   /* con menos de 16px, iOS hace zoom al enfocar el campo */
}
.login-input:focus { outline: none; border-color: var(--info); }
.login-button {
  padding: 12px;
  border: none;
  border-radius: 9px;
  background: var(--ok);
  color: var(--on-accent);
  font-size: 15px;
  font-weight: 600;
  cursor: pointer;
}
.login-button:disabled { opacity: 0.6; cursor: default; }
.login-error { min-height: 18px; font-size: 13px; color: var(--danger); }

/* ---- Celular ---- */
@media (max-width: 600px) {
  .topbar { padding: 0 12px; gap: 8px; }
  .topbar h1 { font-size: 15px; }
  .topbar .subtitle { display: none; }
  .back-button,
  .topbar-button { padding: 6px 10px; }

  /* El estado del enlace queda como un punto. El texto sigue en el DOM para
     los lectores de pantalla, pero no ocupa lugar. */
  .link-status {
    width: 10px;
    height: 10px;
    padding: 0;
    font-size: 0;
    border-radius: 50%;
  }
  .link-status.up { background: var(--ok); }

  .plant-body { padding: 16px; }

  /* Las tres bombas en una fila, en vez de dos y una suelta. */
  .pumps { grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 10px; margin-bottom: 24px; }
  .pump { padding: 14px 6px; }
  .pump-icon { width: 44px; height: 44px; font-size: 20px; margin-bottom: 8px; }
  .pump-name { font-size: 14px; }
  .pump-status { font-size: 10px; letter-spacing: 0.2px; }

  /* Señales en dos columnas y más compactas: son 18 y en una sola columna
     el detalle de planta era un scroll interminable. */
  .signals { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 24px; }
  .signal { padding: 12px; gap: 10px; align-items: flex-start; }
  .signal-light { width: 12px; height: 12px; margin-top: 3px; }
  .signal-name { font-size: 13px; overflow-wrap: anywhere; }

  .bits-panel { padding: 12px; }
  .bits { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}

/* Pantallas muy angostas: las señales vuelven a una columna. */
@media (max-width: 340px) {
  .signals { grid-template-columns: 1fr; }
}

/* ---- Franja de estados de las bombas ----
   Colores de ESTADO, no de identidad: el verde significa "en marcha", no
   "bomba 2". Cada fila lleva su nombre, así que la identidad no depende del
   color. Verde y ámbar quedan cerca bajo daltonismo (medido: ΔE 5.7 protan),
   por eso el hueco además va rayado y con su clave en la referencia: la
   distinción nunca se apoya solo en el tono. */
.timeline { margin-bottom: 24px; }
.timeline-defs { position: absolute; width: 0; height: 0; }

.timeline-row {
  display: grid;
  grid-template-columns: 84px 1fr;
  align-items: center;
  gap: 10px;
  margin-bottom: 4px;
}
/* El nombre y los números van juntos a la izquierda: en el celular, una
   columna a la derecha se partía en dos líneas y le comía ancho al gráfico. */
.timeline-name { font-size: 12px; color: var(--text); line-height: 1.25; }
.timeline-total {
  display: block;
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.timeline-track { display: block; overflow: visible; }

.tl-bg   { fill: var(--surface-2); }
.tl-grid { stroke: var(--border); stroke-width: 1; }
.tl-on   { fill: var(--ok); }

/* Comandada y sin arrancar: contorno y no relleno. El relleno verde significa
   "entregó agua", y acá justamente no la entregó — había orden y no marcha.
   Pintarlo relleno de rojo competiría con el verde por el mismo significado. */
.tl-nostart {
  fill: var(--danger-bg);
  stroke: var(--danger);
  stroke-width: 1.5;
  stroke-dasharray: 3 2;
}

/* Lo que no se sabe no se pinta como apagado. */
.tl-unknown-line { stroke: var(--idle); stroke-width: 2; }
/* El hueco tapa lo que haya debajo: durante un hueco no sabemos qué pasó. */
.tl-gap-bg   { fill: var(--warn-bg); }
.tl-gap-line { stroke: var(--warn); stroke-width: 1; opacity: 0.32; }

.tl-inferred {
  stroke: var(--text);
  stroke-width: 1;
  stroke-dasharray: 2 2;
  opacity: 0.7;
}

.timeline-axis { position: relative; height: 14px; }
.timeline-axis span {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.timeline-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 8px;
  font-size: 11px;
  color: var(--muted);
}
.timeline-key { display: inline-flex; align-items: center; gap: 5px; }
.timeline-key i {
  width: 14px;
  height: 10px;
  border-radius: 2px;
  flex-shrink: 0;
}
.timeline-key .key-on { background: var(--ok); }
/* Igual que .tl-nostart en la franja: contorno, no relleno. */
.key-nostart {
  background: var(--danger-bg);
  border: 1.5px dashed var(--danger);
}
.timeline-key .key-unknown {
  background: repeating-linear-gradient(45deg, var(--idle) 0 2px, transparent 2px 4px);
}
.timeline-key .key-gap {
  background: repeating-linear-gradient(45deg, var(--warn) 0 2px, var(--warn-bg) 2px 4px);
}
.timeline-key .key-inferred {
  height: 0;
  border-top: 1px dashed var(--text);
  opacity: 0.7;
}
.timeline-key .key-count { display: none; }

/* CURVA DE DEMANDA
   Una barra por hora, mismo eje de tiempo y mismas columnas que la franja de
   arriba, así se leen juntas sin medir nada. Serie única: no lleva referencia
   de colores, el título la nombra. El verde es el mismo de "bombeando" en la
   franja -- la barra ES tiempo de bombeo agregado, no una categoría nueva.
   El rayado del hueco es el mismo patrón, por la misma razón: distinguir
   "no pasó nada" de "nadie estaba anotando" no puede depender del tono. */
.demand { margin-bottom: 24px; }
.demand-plot { display: block; overflow: visible; }

.demand-yaxis { position: relative; }
.demand-ytick {
  position: absolute;
  right: 0;
  transform: translateY(-50%);
  font-size: 10px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.demand-ytick.reference { color: var(--text); }

.demand-caption {
  margin: 0 0 8px 94px;
  font-size: 11px;
  color: var(--muted);
}

.demand-bar { fill: var(--ok); }
/* Una bomba que no paró en toda la hora: el valor es cota inferior. Se puntea
   el techo, que es lo único incierto -- encerrar la barra entera en un
   punteado tapaba el gráfico, porque en esta planta casi todas las horas son
   así. */
.demand-open {
  stroke: var(--text);
  stroke-width: 1.5;
  stroke-dasharray: 3 2;
  opacity: 0.85;
}
/* Hora medida a medias: rayas sobre la barra, sin fondo, para que el valor se
   siga leyendo. */
.dm-partial-line { stroke: var(--warn); stroke-width: 1; opacity: 0.5; }
.demand-partial { pointer-events: none; }
.demand-blind { pointer-events: all; }

/* La capacidad de una bomba: la referencia que convierte la curva en una
   respuesta operativa (arriba de la linea, con una bomba no alcanza). */
.demand-ref {
  stroke: var(--text);
  stroke-width: 1;
  stroke-dasharray: 4 3;
  opacity: 0.55;
}

.demand-note {
  margin: 6px 0 0 94px;
  font-size: 11px;
  color: var(--muted);
}

@media (max-width: 600px) {
  .timeline-row { grid-template-columns: 76px 1fr; gap: 8px; }
  .timeline-name { font-size: 11px; }
  .demand-note, .demand-caption { margin-left: 84px; }
}

/* ---- Campana de alarmas ----
   El botón vive en la barra de arriba (uno por vista) y el panel es único,
   anclado abajo a la derecha de la barra. Ver alarm-bell.js. */
.bell-button { position: relative; overflow: visible; }
.bell-button .bell-icon { display: block; margin: 0 auto; }
/* El trazo se tiñe cuando hay alarmas; el número dice cuántas. */
.bell-button.has-alarms { color: var(--danger); border-color: var(--danger); }

.bell-count {
  position: absolute;
  top: -5px;
  right: -5px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--danger);
  color: var(--on-accent);
  font-size: 10.5px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

.bell-panel {
  position: fixed;
  top: 58px;                 /* justo debajo de la barra, que mide 52 */
  right: 12px;
  z-index: 900;              /* sobre el mapa de Leaflet, debajo del login */
  width: min(380px, calc(100vw - 24px));
  max-height: min(70vh, 520px);
  overflow-y: auto;
  padding: 12px;
  border-radius: 12px;
  border: 1px solid var(--border);
  background: var(--overlay);
  backdrop-filter: blur(6px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}
.bell-title    { margin: 0 0 8px; font-size: 13px; font-weight: 600; color: var(--text); }
.bell-subtitle { margin: 12px 0 6px; font-size: 11px; text-transform: uppercase;
                 letter-spacing: 0.08em; color: var(--muted); }
.bell-list     { list-style: none; margin: 0; padding: 0; display: flex;
                 flex-direction: column; gap: 4px; }
.bell-item {
  display: flex;
  gap: 8px;
  padding: 7px 9px;
  border-radius: 8px;
  border-left: 3px solid var(--idle);
  background: var(--surface);
  font-size: 12.5px;
  line-height: 1.35;
}
.bell-item.critical { border-left-color: var(--danger); }
.bell-item.warning  { border-left-color: var(--warn); }
/* Las pasadas van apagadas: el color fuerte se reserva para lo que está activo
   ahora, si no la lista entera grita lo mismo. */
.bell-item.past     { opacity: 0.7; }
.bell-time  { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.bell-plant { color: var(--muted); white-space: nowrap; }

/* ---- Vista Sistema: el cluster ---- */
.system-summary { color: var(--muted); font-size: 13px; margin: 0 0 12px; }
.system-warning {
  background: var(--danger-bg); color: var(--danger);
  border-radius: 8px; padding: 10px 14px; font-size: 13px; margin: 0 0 12px;
}
.system-map { display: block; width: 100%; max-width: 420px; margin: 0 auto 20px; }
.system-map .route { stroke-width: 2; }
.system-map .route.up { stroke: var(--ok); }
.system-map .route.down { stroke: var(--idle); stroke-dasharray: 5 5; }
.system-map .node circle { stroke-width: 2; fill: var(--surface); }
.system-map .node.ok circle { stroke: var(--ok); fill: var(--ok-bg); }
.system-map .node.nats circle { stroke: var(--warn); fill: var(--warn-bg); }
.system-map .node.down circle { stroke: var(--danger); fill: var(--danger-bg); }
.system-map text { text-anchor: middle; fill: var(--text); }
.system-map .node-number { font-size: 14px; font-weight: 600; }
.system-map .node-label { font-size: 11px; fill: var(--muted); }

.node-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.node-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: 10px; padding: 12px 14px; font-size: 13px;
}
.node-card.down { opacity: 0.6; }
.node-card-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.node-region { color: var(--muted); font-size: 12px; }
.node-badge { margin-left: auto; font-size: 11px; padding: 2px 8px; border-radius: 99px; }
.node-badge.ok { background: var(--ok-bg); color: var(--ok); }
.node-badge.nats { background: var(--warn-bg); color: var(--warn); }
.node-badge.down { background: var(--danger-bg); color: var(--danger); }
.node-section { color: var(--muted); font-size: 11px; margin: 12px 0 4px; text-transform: uppercase; letter-spacing: 0.04em; }
.node-clients { list-style: none; margin: 0; padding: 0; }
.node-clients li { display: flex; justify-content: space-between; gap: 8px; padding: 3px 0; }
.node-clients li.gateway span { color: var(--info); font-weight: 600; }
.node-clients li.empty { color: var(--muted); }
.node-clients code { color: var(--muted); font-size: 11px; }
.node-own { color: var(--muted); font-size: 12px; margin: 10px 0 0; }
