/*
 * Torre de Control GIRAG — hoja de estilos de public/index.html.
 *
 * Tokens tomados literalmente de DESIGN.md ("AeroLogic Professional"):
 * navy #000062 como primario (headers/navegación), naranja #fd7c11 como
 * acento (CTAs/alertas), Inter como tipografía, radios suaves (4-8px),
 * tarjetas blancas con borde gris claro, zebra striping en tablas.
 * No se usa ningún framework de CSS (ver decisión de tecnología en el
 * informe): variables + clases utilitarias mínimas, pensadas primero para
 * una pantalla grande/proyector (alta densidad, texto legible a distancia)
 * y que no se rompan en una laptop.
 */

:root {
  /* Superficie / neutrales */
  --surface: #f9f9fc;
  --surface-container-lowest: #ffffff;
  --surface-container-low: #f3f3f6;
  --surface-container: #eeeef0;
  --surface-container-high: #e8e8ea;
  --on-surface: #1a1c1e;
  --on-surface-variant: #454654;
  --outline: #767685;
  --outline-variant: #c6c5d6;

  /* Marca */
  --primary: #000062;
  --primary-container: #04049c;
  --on-primary: #ffffff;
  --accent: #fd7c11;
  --accent-strong: #c2410c;
  --accent-tint: #fff1e2;

  /* Funcionales */
  --success: #147a45;
  --success-container: #d7f0e1;
  --on-success-container: #0b5c33;
  --warning: #a16207;
  --warning-container: #fef3c7;
  --on-warning-container: #78350f;
  --error: #ba1a1a;
  --error-container: #ffdad6;
  --on-error-container: #93000a;
  --info: #1a4fb0;
  --info-container: #dce9fd;
  --on-info-container: #163e8a;

  /* Categorías de hitos (Gantt) — LA ÚNICA paleta CUALITATIVA del proyecto.
     Es la excepción deliberada a "no inventar colores fuera de los tokens":
     los tokens de arriba son SEMÁNTICOS (--error = algo salió mal, --success
     = terminó bien) y usarlos para distinguir categorías haría leer
     "Verificación de carga bajando" como un error. Aquí el color no
     significa estado, solo IDENTIDAD: cada categoría del flujo canónico
     tiene un índice fijo (categoriaOrden, 0-7; el 8 es el respaldo de
     SIN_CATEGORIA), así que la misma categoría se ve igual en todas las
     filas y en todos los vuelos.
     Criterios: hues alternados para que categorías CONSECUTIVAS —las más
     propensas a solaparse en el tiempo y quedar una junto a otra— nunca
     tengan colores parecidos; luminosidad baja y uniforme (~35-45%) para
     que funcionen como BORDE y como texto sobre fondo claro; anclada en la
     marca (0 = navy de --primary, 7 = naranja de --accent-strong, que además
     cierra el flujo) en vez de un arcoíris genérico de librería.
     Cada color va con su tinte muy claro para el relleno del marco, misma
     lógica que --info / --info-container. */
  --cat-0: #1f3c88; /* Verificación de carga bajando — azul de marca */
  --cat-0-tint: #e3e9f8;
  --cat-1: #1a7a5e; /* Actividades de verificación y socialización */
  --cat-1-tint: #dcf0e8;
  --cat-2: #6b3fa0; /* Actividades con aeronave en tierra */
  --cat-2-tint: #eee3f8;
  --cat-3: #7a6a00; /* MD */
  --cat-3-tint: #f4eed3;
  --cat-4: #0f6f8c; /* LT */
  --cat-4-tint: #daeef5;
  --cat-5: #a3195b; /* LD */
  --cat-5-tint: #fbe1ec;
  --cat-6: #3f7a1f; /* BULK */
  --cat-6-tint: #e6f2dc;
  --cat-7: #c2410c; /* Salida — naranja de marca, cierra el flujo */
  --cat-7-tint: #ffe9d9;
  --cat-8: #5a5a6b; /* respaldo: hito sin categoría en el catálogo */
  --cat-8-tint: #eaeaef;

  --radius-sm: 4px;
  --radius-md: 8px;
  --radius-lg: 14px;
  --shadow-card: 0 1px 3px rgba(20, 24, 38, 0.08);
  --shadow-elevated: 0 4px 20px rgba(0, 0, 0, 0.08);

  color-scheme: light;
}

* {
  box-sizing: border-box;
}

html,
body {
  margin: 0;
  padding: 0;
  height: 100%;
}

body {
  background: var(--surface);
  color: var(--on-surface);
  font-family: 'Inter', system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
}

.material-symbols-outlined {
  font-variation-settings: 'FILL' 0, 'wght' 500, 'GRAD' 0, 'opsz' 24;
  vertical-align: middle;
  line-height: 1;
}

/* ── Barra superior ──────────────────────────────────────────────────── */
.app-header {
  background: var(--primary);
  color: var(--on-primary);
  padding: 14px 28px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  flex-wrap: wrap;
  position: sticky;
  top: 0;
  z-index: 10;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.15);
}

.app-brand {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
}

/* Wordmark real de GIRAG (blanco, para la barra navy) en vez del cuadrado
   con una "G" sintética que había antes — mismo alto aproximado que el
   bloque de texto de al lado para que seve al mismo peso visual. */
.app-brand-logo {
  height: 34px;
  width: auto;
  flex-shrink: 0;
}

.app-brand-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.app-brand-title {
  font-weight: 700;
  font-size: 18px;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

.app-brand-sub {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #bfc2ff;
}

.app-status {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-wrap: wrap;
}

.app-clock {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: 26px;
  letter-spacing: -0.01em;
  min-width: 108px;
  text-align: right;
}

.conn-indicator {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: rgba(255, 255, 255, 0.1);
  padding: 7px 14px;
  border-radius: 999px;
}

.conn-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: #767685;
  flex-shrink: 0;
}

.conn-indicator.live .conn-dot {
  background: #34d399;
  box-shadow: 0 0 0 3px rgba(52, 211, 153, 0.25);
  animation: pulse-dot 1.8s ease-in-out infinite;
}

.conn-indicator.down .conn-dot {
  background: #fca5a5;
}

.conn-indicator.down {
  color: #ffd9d9;
}

@keyframes pulse-dot {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.45; }
}

.updated-at {
  font-size: 11px;
  color: #c7c9ff;
  font-weight: 600;
}

/* ── Contenido principal ─────────────────────────────────────────────── */
.app-main {
  padding: 24px 28px 48px;
  max-width: 2400px;
  margin: 0 auto;
}

.toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 18px;
  flex-wrap: wrap;
}

.toolbar-title {
  font-size: 15px;
  font-weight: 700;
  color: var(--on-surface-variant);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  display: flex;
  align-items: center;
  gap: 8px;
}

.count-pill {
  background: var(--primary);
  color: #fff;
  font-weight: 800;
  font-size: 13px;
  padding: 2px 10px;
  border-radius: 999px;
}

.empty-state {
  background: #fff;
  border: 1px dashed var(--outline-variant);
  border-radius: var(--radius-lg);
  padding: 64px 24px;
  text-align: center;
  color: var(--on-surface-variant);
}

.empty-state .material-symbols-outlined {
  font-size: 48px;
  color: var(--outline);
  margin-bottom: 12px;
}

.empty-state-title {
  font-size: 18px;
  font-weight: 700;
  color: var(--on-surface);
  margin-bottom: 4px;
}

.error-banner {
  background: var(--error-container);
  color: var(--on-error-container);
  border-radius: var(--radius-md);
  padding: 12px 16px;
  font-weight: 600;
  font-size: 13px;
  margin-bottom: 16px;
  display: flex;
  align-items: center;
  gap: 8px;
}

/* ── Grilla de vuelos ────────────────────────────────────────────────── */
.flights-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(380px, 1fr));
  gap: 18px;
}

.flight-card {
  background: var(--surface-container-lowest);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  /* Ítem de grid: sin esto, "min-width: auto" deja que el min-content de sus
     descendientes (ej. el texto de un evento largo) fuerce la columna 1fr a
     crecer más allá del contenedor en mobile, aunque .event-text ya sepa
     recortarse con ellipsis. */
  min-width: 0;
  cursor: pointer;
  transition: box-shadow 0.15s ease, transform 0.15s ease, border-color 0.15s ease;
  border-left: 5px solid var(--outline-variant);
}

.flight-card:hover {
  box-shadow: var(--shadow-elevated);
  transform: translateY(-2px);
  border-color: var(--accent);
}

.flight-card.state-proceso { border-left-color: var(--accent); }
.flight-card.state-programado { border-left-color: var(--info); }
.flight-card.state-otro { border-left-color: var(--outline); }


.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 6px 11px;
  border-radius: 999px;
  white-space: nowrap;
}

.status-badge::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.status-badge.state-proceso { background: var(--accent-tint); color: var(--accent-strong); }
.status-badge.state-programado { background: var(--info-container); color: var(--on-info-container); }
.status-badge.state-otro { background: var(--surface-container); color: var(--on-surface-variant); }

/* .flight-meta / .meta-item / .meta-label / .meta-value desaparecieron con
   UX 2 (ver PLAN_EQUIPOS_Y_BUGS.md): el bloque de metadatos del vuelo
   (Cliente / Ruta / Base / Posición) que pintaban ahora es la pestaña "Datos"
   de la tarjeta azul, dentro de .op-header-mount — ver /css/tarjeta-azul.css.
   Igual suerte corrieron .flight-card-head / .flight-number /
   .flight-matricula, más arriba: el nº de vuelo, la matrícula y el badge de
   estado los muestra la pestaña "Vuelo" de esa misma tarjeta, y dejarlos
   además en la cabecera de la .flight-card era mostrar dos veces lo mismo. */

/* Envoltorio de progreso + últimos eventos. Existe porque renderTarjetas()
   repinta SOLO esta parte en cada refresco (cada 30s y en cada señal del
   WebSocket), dejando intacto el montaje de la tarjeta azul y su estado
   —pestaña activa, mapa ya cargado—: necesita un contenedor propio al que
   asignarle innerHTML. Reproduce el gap de .flight-card para que progreso y
   eventos queden separados igual que cuando eran hermanos directos. */
.flight-card-detalle {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}
/* Fila del contador de tiempo restante para STD. La barra de avance y el
   conteo "X/Y hitos" que vivían acá se quitaron: la tarjeta azul ya muestra
   ese progreso en su cabecera (ver renderDetalleCard en dashboard.js). */
.flight-timer-row {
  border-top: 1px solid var(--surface-container-high);
  padding-top: 12px;
}

.std-timer {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
}

.std-timer.timer-normal { color: var(--success); }
.std-timer.timer-warning { color: var(--warning); }
.std-timer.timer-critical { color: var(--error); }
.std-timer.timer-none { color: var(--outline); }

/* ── Últimos eventos ─────────────────────────────────────────────────── */
.event-list {
  border-top: 1px solid var(--surface-container-high);
  padding-top: 10px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.event-list-title {
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--outline);
  margin: 0 0 2px;
}

.event-item {
  display: flex;
  align-items: center;
  gap: 10px;
}

.event-dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: #fff;
}

.event-dot .material-symbols-outlined {
  font-size: 14px;
}

.event-dot.tipo-operacion { background: var(--accent); }
.event-dot.tipo-novedad { background: var(--warning); }

.event-text {
  min-width: 0;
  flex: 1 1 auto;
  font-size: 13px;
  font-weight: 600;
  color: var(--on-surface);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.event-time {
  font-size: 11px;
  font-weight: 700;
  color: var(--outline);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

.no-events {
  font-size: 12px;
  color: var(--outline);
  font-style: italic;
}

.event-list-locked {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--outline);
}

.event-list-locked .material-symbols-outlined {
  font-size: 15px;
}

/* ── Skeleton de carga ───────────────────────────────────────────────── */
.skeleton-card {
  background: #fff;
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg);
  padding: 18px 20px;
  height: 260px;
  overflow: hidden;
  position: relative;
}

.skeleton-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.04), transparent);
  animation: shimmer 1.4s infinite;
}

@keyframes shimmer {
  0% { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* ── Responsive: laptop / tablet ─────────────────────────────────────── */
@media (max-width: 1024px) {
  .app-header { padding: 12px 18px; }
  .app-main { padding: 18px 18px 40px; }
  .flights-grid { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
  .app-clock { font-size: 20px; min-width: 88px; }
}

@media (max-width: 640px) {
  .app-header { flex-direction: column; align-items: flex-start; }
  .app-status { width: 100%; justify-content: space-between; }
  .flights-grid { grid-template-columns: 1fr; }
}

/* ── Auth section en header ───────────────────────────────────────── */
.auth-section {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: 16px;
}

/*
 * .auth-btn vive sobre la barra azul (--primary), no sobre superficie clara:
 * antes usaba los mismos tokens --primary/--on-surface-variant pensados para
 * fondo blanco, así que el botón de login se fundía con el header y el de
 * salir (borde+texto gris oscuro) quedaba casi invisible. Se usa un fondo
 * blanco translúcido para el estado por defecto y el acento naranja para el
 * CTA de login, que sí necesita destacar.
 */
.auth-btn {
  background: var(--accent);
  color: #fff;
  border: none;
  border-radius: var(--radius-sm);
  padding: 8px 16px;
  font-size: 13px;
  font-weight: 700;
  cursor: pointer;
  font-family: inherit;
  transition: background 0.15s;
}

.auth-btn:hover {
  background: var(--accent-strong);
}

.auth-btn-logout {
  background: rgba(255, 255, 255, 0.12);
  border: 1px solid rgba(255, 255, 255, 0.35);
  color: #fff;
  font-weight: 600;
}

.auth-btn-logout:hover {
  background: rgba(255, 255, 255, 0.22);
}

.user-info {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: #fff;
}

/*
 * Seguimiento push (public/js/push.js) — vive dentro del panel de
 * seguimiento flotante de atender.html (#op-push-panel, abierto desde el
 * FAB #btn-push-fab; antes vivía siempre expandido dentro de la tarjeta
 * "Información del vuelo", #card-info-body). Por eso NO lleva su propio
 * fondo/borde/sombra de tarjeta propios: el panel flotante que lo envuelve
 * (.op-search-panel/.op-search-results, reutilizadas en atender.css) ya
 * aporta esa superficie.
 */
.push-panel {
  padding-top: 4px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
}

.push-panel > .material-symbols-outlined {
  color: var(--accent);
  font-size: 26px;
  flex-shrink: 0;
}

.push-panel-texto {
  flex: 1 1 220px;
  font-size: 14px;
  color: var(--on-surface-variant);
}

.push-panel-titulo {
  margin: 0;
  font-weight: 700;
  color: var(--on-surface);
}

.push-panel-subtitulo {
  margin: 2px 0 0;
  font-size: 13px;
}

/* Envoltorio del <select> de nivel de notificaciones (ver render() en
   public/js/push.js): reproduce la misma "píldora" que .op-toggle-all /
   .op-pill-btn (public/css/atender.css) — radio 999px, borde fino, texto
   uppercase — porque un <select> nativo no puede tomar border-radius:999px
   de forma consistente entre navegadores sin perder su apariencia nativa.
   El <select> interno queda transparente y sin flecha del SO
   (appearance:none) y la flecha propia se dibuja con el ::after de abajo,
   así no se duplica con la nativa. El control sigue siendo un <select> real
   (teclado y lectores de pantalla funcionan igual que antes). */
.push-nivel-select-wrap {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  border: 1px solid var(--outline-variant);
  background: var(--surface-container-lowest);
  border-radius: 999px;
  min-height: 36px;
  padding: 0 26px 0 12px;
}

.push-nivel-select-wrap select {
  appearance: none;
  -webkit-appearance: none;
  -moz-appearance: none;
  border: 0;
  background: transparent;
  padding: 0;
  margin: 0;
  font-family: inherit;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--on-surface-variant);
  cursor: pointer;
  width: 100%;
}

.push-nivel-select-wrap select:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}

.push-nivel-select-wrap::after {
  content: '';
  position: absolute;
  right: 12px;
  top: 50%;
  width: 7px;
  height: 7px;
  border-right: 2px solid var(--on-surface-variant);
  border-bottom: 2px solid var(--on-surface-variant);
  transform: translateY(-65%) rotate(45deg);
  pointer-events: none;
}

.push-panel-activo > .material-symbols-outlined {
  color: var(--success);
}

.push-panel-error .material-symbols-outlined {
  color: var(--error);
}

@media (max-width: 640px) {
  .push-panel { flex-direction: column; align-items: stretch; }
  /* .push-panel-texto trae flex:1 1 220px pensado para layout en fila (220px
     de ANCHO mínimo). En columna, flex-basis aplica al eje vertical: sin
     este reset, el bloque de texto reclamaba ~220px de ALTO + flex-grow,
     dejando un hueco vacío enorme antes del selector/botón de abajo. */
  .push-panel-texto { flex: 0 1 auto; }
  .push-panel .push-nivel-select-wrap,
  .push-panel .op-pill-btn {
    width: 100%;
  }
  /* padding-right fijo (26px) reservado para la flecha: al pasar a 100% de
     ancho, centramos el texto del <select> igual que el resto de píldoras. */
  .push-panel .push-nivel-select-wrap select { text-align: center; }
}

.user-email {
  max-width: 180px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════════
   Vista Gantt (alterna con la grilla de tarjetas)
   ════════════════════════════════════════════════════════════════════════
   Dos vistas del MISMO conjunto de vuelos (ver render() en dashboard.js):
   #grid (tarjetas, la de siempre) y #gantt. La que no está activa se apaga
   con el atributo `hidden`; como ambas tienen `display` propio (grid/block),
   hace falta una regla explícita —un `[hidden]` a secas pierde el empate de
   especificidad contra `.flights-grid { display: grid }`. */
.flights-grid[hidden],
.gantt-view[hidden],
.view-switch[hidden] {
  display: none;
}

/* ── Selector de vista (Tarjetas | Gantt) ────────────────────────────────
   No existía un "segmented control" en el proyecto. Se construye con el
   mismo lenguaje de píldora que .op-toggle-all/.op-pill-btn de atender.css
   (radio 999px, 11px uppercase peso 800, alto táctil), pero como grupo: un
   riel en var(--surface-container) y la opción activa rellena de
   var(--primary). Todos los valores salen de los tokens de :root. */
.view-switch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  background: var(--surface-container);
  border: 1px solid var(--outline-variant);
  border-radius: 999px;
  flex-shrink: 0;
}

.view-switch-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border: 0;
  background: transparent;
  color: var(--on-surface-variant);
  border-radius: 999px;
  padding: 6px 14px;
  min-height: 34px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease, color 0.15s ease;
}

.view-switch-btn .material-symbols-outlined { font-size: 16px; }
.view-switch-btn:hover { color: var(--primary); }
.view-switch-btn:active { filter: brightness(0.97); }

.view-switch-btn.is-active {
  background: var(--primary);
  color: var(--on-primary);
  box-shadow: var(--shadow-card);
}

.view-switch-btn:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: 2px;
}

/* ── Panel del Gantt ─────────────────────────────────────────────────────
   Estructura (la arma renderGantt() en dashboard.js):
     .gantt-panel
       └ .gantt-scroll            ← único scroll horizontal
           └ .gantt-inner         ← ancho mínimo: fuerza el scroll en móvil
               ├ .gantt-head      ← etiquetas de hora (ticks) + chip "ahora"
               └ .gantt-body      ← una .gantt-row por vuelo
                   ├ .gantt-grid-layer  ← líneas verticales + línea "ahora",
                   │                      dibujadas UNA vez para todas las filas
                   └ .gantt-row × N
       └ .gantt-foot              ← leyenda + rango + pista de scroll (móvil)

   .gantt-head/.gantt-row comparten el mismo grid-template-columns, así que
   la columna de etiquetas y la pista quedan alineadas sin cálculos en JS.

   Dentro de la pista de cada vuelo:
     .gantt-track (alto = --carriles × --lane-h, lo fija JS por fila)
       └ .gantt-lane × N          ← un carril; --carril da su desplazamiento
           └ .gantt-banda         ← capa que cubre TODA la pista, con el
             │                      color de la categoría en --cat
             ├ .gantt-cat-frame   ← el MARCO (de --ini a --fin)
             ├ .gantt-cat-tag     ← rótulo de la categoría, sobre el marco
             └ .gantt-mark × N    ← un hito de esa categoría

   Todo se posiciona con `left: %` sobre un rango de tiempo común a TODAS
   las filas; por eso .gantt-banda cubre la pista completa en vez de
   limitarse al marco: así el % de un hito significa lo mismo dentro del
   marco que fuera. NO hay barras de "programado" —el catálogo de hitos no
   tiene tiempo estimado—, solo el instante real en que se registró cada
   hito, agrupado en el marco de su categoría. */
.gantt-view {
  --gantt-label: 168px;
  --gantt-row-h: 48px;
  --gantt-track-min: 420px;

  /* Geometría de un carril. El alto de la FILA no es fijo: es
     --carriles × --lane-h, y --carriles lo pone dashboard.js por fila según
     cuántas categorías concurrentes tenga ese vuelo.
     --lane-h era 52px cuando cada marca llevaba su nombre inline (hacía falta
     aire arriba y abajo de la línea de puntos para las etiquetas alternadas);
     ahora que la marca es solo el punto, al carril le basta con el rótulo de
     categoría + el marco, y la fila se compacta. */
  --lane-h: 42px;
  --lane-gap: 4px;
  --cat-tag-h: 13px;
  /* El marco empieza 4px por encima de donde acaba el rótulo, para que el
     rótulo monte sobre el borde superior (efecto "legend" de fieldset). */
  --frame-top: calc(var(--cat-tag-h) - 4px);
  /* Línea de puntos: centro vertical del marco. */
  --dot-y: calc((var(--frame-top) + var(--lane-h) - var(--lane-gap)) / 2);
  /* Ancho mínimo de un marco: una categoría con un solo hito (inicio ===
     fin) tendría ancho 0 y no se vería como marco. */
  --frame-min-w: 28px;
}

.gantt-panel {
  background: var(--surface-container-lowest);
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.gantt-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
}

.gantt-inner {
  min-width: 100%;
  width: max-content;
}

.gantt-head,
.gantt-row {
  display: grid;
  grid-template-columns: var(--gantt-label) minmax(var(--gantt-track-min), 1fr);
}

.gantt-head {
  background: var(--surface-container-low);
  border-bottom: 1px solid var(--outline-variant);
  height: 32px;
}

/* La columna de etiquetas queda pegada al borde izquierdo mientras se
   desplaza la pista (móvil/pantallas angostas): sin esto, al hacer scroll
   se pierde de vista de qué vuelo es cada fila. Necesita fondo opaco
   porque pasa por encima de las líneas de la cuadrícula. */
.gantt-head-label,
.gantt-row-label {
  position: sticky;
  left: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 1px;
  min-width: 0;
  padding: 0 12px;
  border-right: 1px solid var(--surface-container-high);
}

.gantt-head-label {
  background: var(--surface-container-low);
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--outline);
}

.gantt-head-track {
  position: relative;
}

.gantt-tick {
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%);
  font-size: 10px;
  font-weight: 700;
  color: var(--outline);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.gantt-ahora-chip {
  position: absolute;
  top: 50%;
  transform: translate(-100%, -50%);
  margin-left: -6px;
  background: var(--primary);
  color: var(--on-primary);
  font-size: 9px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 2px 7px;
  border-radius: 999px;
  white-space: nowrap;
}

.gantt-body {
  position: relative;
}

.gantt-grid-layer {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--gantt-label);
  right: 0;
  pointer-events: none;
  z-index: 0;
}

.gantt-grid-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--surface-container-high);
}

.gantt-ahora-line {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0;
  border-left: 2px dashed var(--primary);
  opacity: 0.55;
}

/* El alto crece con el número de carriles que necesite ESTE vuelo: un vuelo
   con categorías realmente concurrentes ocupa más alto que uno cuyas
   categorías van en secuencia. --carriles llega inline desde dashboard.js
   (1 también en las filas sin hitos y en las bloqueadas por falta de
   sesión). Va en la fila, no en la pista, para que la columna sticky de
   etiquetas crezca con ella. */
.gantt-row {
  position: relative;
  z-index: 1;
  min-height: max(var(--gantt-row-h), calc(var(--carriles, 1) * var(--lane-h) + 4px));
  border-bottom: 1px solid var(--surface-container-high);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.15s ease;
}

.gantt-row:last-child { border-bottom: 0; }

.gantt-row-label {
  background: var(--surface-container-lowest);
  border-left: 5px solid var(--outline-variant);
  transition: background 0.15s ease;
}

/* Mismo código de color por estado que el borde izquierdo de .flight-card,
   para que una fila y su tarjeta se lean como el mismo vuelo. */
.gantt-row.state-proceso .gantt-row-label { border-left-color: var(--accent); }
.gantt-row.state-programado .gantt-row-label { border-left-color: var(--info); }
.gantt-row.state-otro .gantt-row-label { border-left-color: var(--outline); }

.gantt-row:hover,
.gantt-row:hover .gantt-row-label {
  background: var(--accent-tint);
}

.gantt-row:focus-visible {
  outline: 2px solid var(--primary);
  outline-offset: -2px;
}

.gantt-row-vuelo {
  font-size: 15px;
  font-weight: 800;
  color: var(--primary);
  line-height: 1.15;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gantt-row-sub {
  font-size: 10.5px;
  font-weight: 700;
  color: var(--on-surface-variant);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gantt-track {
  position: relative;
  min-height: var(--gantt-row-h);
}

/* Un carril. Se apilan verticalmente con --carril (0, 1, 2…) en vez de con
   flujo normal porque cada carril es una capa de posicionamiento absoluto:
   sus marcos y marcas se ubican por porcentaje del ancho total de la pista. */
.gantt-lane {
  position: absolute;
  left: 0;
  right: 0;
  top: calc(2px + var(--carril) * var(--lane-h));
  height: var(--lane-h);
}

/* Una banda = una categoría con al menos un hito ejecutado en este vuelo.
   Cubre TODA la pista (no solo su marco) a propósito: así --ini/--fin/--pos
   son porcentajes del ancho de la pista —que es lo que significan en el eje
   de tiempo compartido— y no de una caja recortada al marco. Como es un
   overlay que se superpone al de las demás bandas, no captura el puntero:
   solo lo hacen el rótulo y los puntos, que llevan el `data-tip` con el
   detalle. El clic sigue llegando a la fila (navega al detalle del vuelo),
   toque el fondo vacío o una banda. */
.gantt-banda {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/* El MARCO de la categoría: borde de su color, relleno con su tinte.
   El ancho sale del rango real (--fin - --ini) pero con dos correcciones:
   un mínimo (--frame-min-w) para que una categoría de un solo hito siga
   leyéndose como marco, y un tope (--max-w) para que ese mínimo no invada
   el hueco de la siguiente banda del mismo carril. */
.gantt-cat-frame {
  position: absolute;
  left: min(var(--ini), calc(100% - var(--frame-min-w)));
  width: min(max(calc(var(--fin) - var(--ini)), var(--frame-min-w)), var(--max-w));
  top: var(--frame-top);
  bottom: var(--lane-gap);
  border: 1.5px solid var(--cat);
  border-radius: var(--radius-md);
  background: var(--cat-tint);
}

/* Rótulo de la categoría, montado sobre el borde superior izquierdo del
   marco. Sólido y en el color de la categoría (no en tinte) para que sea el
   ancla visual del grupo. Los nombres largos ("Actividades de verificación y
   socialización") se recortan con ellipsis y quedan completos en el tooltip;
   el ancho máximo lo limita el hueco hasta la banda siguiente del mismo
   carril (--esp-cat), con un piso para que nunca quede ilegible. El tope
   duro es holgado (240px) porque el rótulo de categoría ya no compite con
   ningún texto de marca: es el único rótulo del carril. */
.gantt-cat-tag {
  position: absolute;
  z-index: 3;
  left: min(var(--ini), calc(100% - var(--frame-min-w)));
  top: 0;
  max-width: max(min(var(--esp-cat), 240px), 34px);
  height: var(--cat-tag-h);
  padding: 0 6px;
  border-radius: 999px;
  background: var(--cat);
  color: #fff;
  font-size: 9px;
  font-weight: 800;
  line-height: var(--cat-tag-h);
  letter-spacing: 0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: auto;
}

/* Una marca por hito ejecutado, dentro del marco de su categoría: SOLO el
   punto, sin texto al lado. El nombre del hito llega por el tooltip
   (`data-tip` en la marca) al pasar el mouse, tocar o enfocar con teclado —
   antes se pintaba también inline, pero con varios hitos cercanos en el
   tiempo esos nombres se solapaban y quedaban ilegibles. El único rótulo
   siempre visible dentro del marco es el de la categoría. */
.gantt-mark {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
}

.gantt-mark-dot {
  position: absolute;
  top: var(--dot-y);
  left: var(--pos);
  transform: translate(-50%, -50%);
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--cat);
  border: 2px solid var(--surface-container-lowest);
  box-shadow: 0 0 0 1px var(--cat);
  pointer-events: auto;
}

/* El hito más reciente de cada vuelo es "dónde va" la atención ahora mismo,
   lo primero que se busca en la pantalla. El punto conserva el color de su
   categoría (si no, se perdería a qué grupo pertenece) y lo que lo destaca
   es el halo naranja de marca. */
.gantt-mark-ultimo .gantt-mark-dot {
  box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 5px rgba(253, 124, 17, 0.22);
}

.gantt-sin-hitos {
  position: absolute;
  top: 50%;
  left: 12px;
  transform: translateY(-50%);
  font-size: 11px;
  font-style: italic;
  color: var(--outline);
}

.gantt-row-locked {
  position: absolute;
  top: 50%;
  left: 12px;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 11px;
  font-weight: 600;
  color: var(--outline);
}

.gantt-row-locked .material-symbols-outlined { font-size: 15px; }

.gantt-foot {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px 18px;
  padding: 9px 14px;
  border-top: 1px solid var(--surface-container-high);
  background: var(--surface-container-low);
  font-size: 11px;
  font-weight: 700;
  color: var(--on-surface-variant);
}

.gantt-leyenda {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.gantt-leyenda-titulo {
  font-size: 10px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--outline);
}

/* Misma receta que el marco de la fila (borde de color + tinte), a escala de
   leyenda: la muestra tiene que parecerse a lo que se ve en la pista, no ser
   un punto de color. Solo se listan las categorías presentes en los vuelos
   que se están mostrando, así que la leyenda es dinámica. */
.gantt-leyenda-cat {
  width: 16px;
  height: 11px;
  border: 1.5px solid var(--cat);
  border-radius: 3px;
  background: var(--cat-tint);
  flex-shrink: 0;
}

.gantt-leyenda-sep {
  width: 1px;
  height: 14px;
  background: var(--outline-variant);
  flex-shrink: 0;
}

.gantt-leyenda-punto {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--outline);
  flex-shrink: 0;
}

/* El halo, no el relleno, es lo que marca el último hito (en la pista el
   punto conserva el color de su categoría). */
.gantt-leyenda-punto.es-ultimo {
  background: var(--outline);
  box-shadow: 0 0 0 1.5px var(--accent), 0 0 0 4px rgba(253, 124, 17, 0.22);
}

.gantt-leyenda-linea {
  width: 0;
  height: 12px;
  border-left: 2px dashed var(--primary);
  opacity: 0.55;
  flex-shrink: 0;
}

.gantt-rango {
  margin-left: auto;
  color: var(--outline);
  font-variant-numeric: tabular-nums;
}

/* Solo se anuncia el gesto donde la pista realmente desborda (ver media
   query de abajo). */
.gantt-hint { display: none; }

.gantt-skeleton {
  height: 220px;
  border: 1px solid var(--outline-variant);
  border-radius: var(--radius-lg);
  background: var(--surface-container-lowest);
  overflow: hidden;
  position: relative;
}

.gantt-skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.04), transparent);
  animation: shimmer 1.4s infinite;
}

/* Tooltip propio del Gantt (reemplaza el `title` nativo del navegador, que
   no se puede estilar y que el overflow-x:auto de .gantt-track recortaba
   cerca del borde). Un solo elemento compartido, posicionado por JS
   (ver mostrarTooltipGantt en dashboard.js) con position:fixed — por eso
   vive fuera de .gantt-panel, colgado directo de <body>. */
.gantt-tooltip {
  position: fixed;
  z-index: 60;
  max-width: 260px;
  background: var(--primary);
  color: var(--on-primary);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.35;
  padding: 7px 10px;
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-elevated);
  pointer-events: none;
  white-space: pre-line;
}

.gantt-tooltip::after {
  content: '';
  position: absolute;
  left: var(--flecha-left, 50%);
  transform: translateX(-50%);
  border: 5px solid transparent;
  /* Por defecto el tooltip va ARRIBA del objetivo: la flecha apunta hacia
     abajo, pegada al borde inferior. */
  top: 100%;
  border-top-color: var(--primary);
}

.gantt-tooltip.gantt-tooltip-abajo::after {
  top: auto;
  bottom: 100%;
  border-top-color: transparent;
  border-bottom-color: var(--primary);
}

@media (max-width: 1024px) {
  .gantt-view { --gantt-label: 140px; }
}

/* Móvil: la fila se compacta (etiqueta más angosta, alto menor) pero la
   pista NO se comprime —se le exige un ancho mínimo mayor que la pantalla y
   se desplaza en horizontal—, porque por debajo de ~500px las marcas de
   hitos consecutivos se solapan y el gráfico deja de leerse. La columna de
   etiquetas es sticky, así que el número de vuelo sigue visible mientras se
   desliza la línea de tiempo. */
@media (max-width: 640px) {
  .gantt-view {
    --gantt-label: 126px;
    --gantt-row-h: 44px;
    --gantt-track-min: 640px;
    --lane-h: 40px;
    --lane-gap: 3px;
    --cat-tag-h: 12px;
    --frame-min-w: 26px;
  }

  .gantt-cat-tag { font-size: 8.5px; padding: 0 5px; }

  .gantt-row-vuelo { font-size: 14px; }
  .gantt-foot { gap: 6px 12px; font-size: 10px; }
  .gantt-rango { margin-left: 0; }
  .gantt-hint {
    display: inline-flex;
    align-items: center;
    gap: 5px;
    color: var(--outline);
    font-weight: 600;
  }
  .gantt-hint .material-symbols-outlined { font-size: 14px; }

  /* En pantallas angostas la barra de herramientas apila título y selector;
     el selector ocupa el ancho completo para que ambas opciones tengan un
     área táctil cómoda. */
  .view-switch { width: 100%; }
  .view-switch-btn { flex: 1 1 0; }
}
