/* ── AeroControl Theme ──────────────────────────────── */
/* Tokens live in a single :root further down ("UI foundation"). This file used
   to open with a second, older :root whose values lost to it on source order,
   so a colour fixed here silently had no effect. */

body {
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
}

/* ── Navbar ───────────────────────────────────────── */
.navbar-dark.navy {
  background: var(--ac-navy);
}

.brand-mark {
  width: 2rem;
  height: 2rem;
  flex: 0 0 2rem;
}

.brand-lockup {
  display: grid;
  line-height: 1.05;
}

.brand-subtitle {
  margin-top: 0.18rem;
  color: rgba(255, 255, 255, 0.7);
  font-size: 0.58rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ── Sidebar ──────────────────────────────────────── */
/* LV-63: this used to be two rules -- this bare base one, and a second,
   later one (originally at ~line 719, from the design-tokens pass) that
   redeclared position/width/flex/padding/background with the same (0,1,0)
   specificity. Two same-specificity rules for the same selector means the
   LATER one in the file wins regardless of what @media wraps the other --
   so the mobile off-canvas block below (position: fixed) was losing to this
   rule's desktop values every time, at every viewport. Fused into one
   declaration so there is only one place geometry can come from, and the
   @media (max-width:768px) block right after it wins on mobile the normal
   way: by being more specific to that viewport, not by source-order luck. */
.sidebar {
  position: sticky;
  top: 58px;
  flex: 0 0 var(--ac-sidebar-width);
  width: var(--ac-sidebar-width);
  min-height: calc(100vh - 58px);
  padding: 1rem 0.75rem !important;
  gap: 0.15rem;
  background: var(--ac-surface);
  border-right: 1px solid var(--ac-border);
  /* Transition also fused in (was a separate rule at ~line 649 that listed
     .sidebar for background/color/border/box-shadow only): transform joins
     the list here so the mobile drawer actually animates open/closed
     instead of snapping, and dark/light toggles keep animating too. */
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease,
    box-shadow 0.2s ease, transform 0.2s ease;
}

/* LV-208: el borde arrastrable. Pedido del usuario: *"se podía poner que la
   navegación poder agrandar o desplazar para poder regular el ancho de la
   barra"*. Hasta acá la barra tenía dos estados y nada en medio, y con nombres
   como "Planificación geoespacial" el ancho fijo obliga a elegir entre texto
   cortado y quitarle sitio al contenido — que es distinto de colapsar.

   Se dibuja **sobre** el borde derecho y no ocupa lugar en el flujo: la barra ya
   tiene `border-right`, y meter un elemento de 6 px en el layout habría movido
   todo el contenido. De ahí `position:absolute` con `right:-3px`, centrado sobre
   la línea que ya existe.

   `touch-action:none` es lo que impide que el navegador interprete el arrastre
   como desplazamiento de la página en un táctil. */
.sidebar-resizer {
  position: absolute;
  top: 0;
  right: -3px;
  width: 6px;
  height: 100%;
  cursor: col-resize;
  touch-action: none;
  background: transparent;
  border: 0;
  padding: 0;
  z-index: 3;
}

/* Sólo se insinúa al apuntarlo o al enfocarlo: un tirador siempre visible
   compite con el borde de la barra por la atención, y no es un control que se
   use a diario. El foco **sí** se dibuja siempre que llega por teclado, porque
   si no, quien navega con tabulador no sabría dónde está. */
.sidebar-resizer:hover,
.sidebar-resizer:focus-visible {
  background: var(--ac-primary);
}

.sidebar-resizer:focus-visible {
  outline: 2px solid var(--ac-primary);
  outline-offset: 1px;
}

/* Mientras se arrastra, nada de selección de texto ni de cursores cambiando
   según lo que haya debajo del puntero. */
body.is-resizing-sidebar {
  cursor: col-resize;
  user-select: none;
}

/* Con la barra colapsada el tirador no aplica: el ancho lo manda el estado, y
   dejarlo agarrable permitiría estirar una barra en modo icono a 400 px de
   iconos centrados. El botón `<` es lo que devuelve el estado abierto. */
.sidebar.is-collapsed .sidebar-resizer { display: none; }

/* El cajón móvil se abre a `min(86vw, ...)`: ahí el ancho no se regula, se
   abre o se cierra. */
@media (max-width: 768px) {
  .sidebar-resizer { display: none; }
}

.sidebar a {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ac-navy);
  padding: 0.6rem 0.8rem;
  text-decoration: none;
  border-radius: 6px;
  font-size: 0.92rem;
  transition: background 0.15s ease;
}

.sidebar a:hover {
  background: #e4f8f5;
  color: var(--ac-navy);
}

.sidebar a.active {
  background: #d4f5f0;
  color: #1B2A4A;
  font-weight: 600;
}

.nav-icon {
  width: 1.2rem;
  height: 1.2rem;
  flex: 0 0 1.2rem;
  fill: none;
  stroke: currentColor;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-width: 1.8;
}

/* LV-207: **un color por sección del menú**, no por app de destino.

   Pedido del usuario con captura: *"panel lateral, los colores por región
   mantenerlos separados, con cada uno para ser diferentes, no mezclar"*. La
   clase se asignaba por la app a la que apunta cada fila, así que el color no
   agrupaba ni distinguía: **Informes** mezclaba azul y ámbar adentro,
   **Inventario** mezclaba azul y gris, y tres secciones compartían el azul.
   Ahora la clase la da la sección, y las ocho son distintas.

   Dos de los ocho nombres son nuevos y dos colores se reciclaron: `nav-reports`
   hereda el magenta que `nav-workboard` dejó libre cuando `LV-78` retiró el
   tablero, y `nav-inventory` el teal que usaba `nav-operations`. El único color
   nuevo es el verde de `nav-flight`.

   ⚠️ **Los siete colores anteriores cumplían 3:1 y no se tocaron.** El
   diagnóstico registrado en la fila decía que cinco no llegaban —con cifras como
   `registry 2.41`— y **eso era falso**: calculado sobre los hex del CSS contra
   `--ac-surface`, los ratios reales son 5.59 / 5.91 / 4.73 / 3.74 / 5.70 / 6.04 /
   5.90 en claro y 7.6 a 10.7 en oscuro. Los números del diagnóstico se habían
   medido contra el fondo equivocado. El umbral es 3:1 porque un icono es un
   elemento gráfico (WCAG 1.4.11), el mismo criterio de `LV-185`.

   El verde nuevo se eligió calculando, no a ojo: `#15803d` da **5.02** sobre
   blanco y `#86efac` **11.79** sobre el navy del tema oscuro. El candidato que
   proponía el diagnóstico anterior (`#65a30d`) daba 3.09 en claro — pasaba por
   nueve centésimas, y con cualquier ajuste de fondo dejaba de pasar.

   ⚠️ **Lo que queda sin resolver, y conviene saberlo**: `nav-overview` (teal
   oscuro) y `nav-inventory` (teal) están a distancia RGB 34, así que son los dos
   más parecidos entre sí. Se dejan porque ocupan los **extremos** del menú —Panel
   arriba, Inventario abajo— y nunca se ven contiguos; si algún día molesta, el
   que hay que mover es `overview`, no `inventory`. */
.nav-overview .nav-icon { color: #0b7285; }
.nav-flight .nav-icon { color: #15803d; }
.nav-compliance .nav-icon { color: #b15d00; }
.nav-reports .nav-icon { color: #be185d; }
.nav-maintenance .nav-icon { color: #7c3aed; }
.nav-registry .nav-icon { color: #2463b8; }
.nav-inventory .nav-icon { color: #0d9488; }
.nav-admin .nav-icon { color: #596579; }

.sidebar a.active {
  box-shadow: inset 3px 0 0 var(--ac-turquoise);
}

.sidebar a .badge {
  margin-left: auto;
}

.sidebar-label {
  color: #8892a4;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  padding: 0.8rem 0.8rem 0.2rem;
  font-weight: 600;
}

/* LV-170: el rótulo de grupo ahora es un `<button>`, así que hay que devolverle
   la apariencia de rótulo que el navegador le quita -- sin esto llega con fondo,
   borde, texto centrado y la tipografía del sistema. Ancho completo y `text-align`
   explícito: un botón centra por defecto y el menú entero se leería torcido. */
.nav-group-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  width: 100%;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: left;
}

.nav-group-toggle:hover { color: var(--ac-text-secondary); }

.nav-group-toggle:focus-visible {
  outline: 2px solid var(--ac-primary);
  outline-offset: -2px;
  border-radius: 6px;
}

/* La flecha gira, y es lo único que dice si el grupo está plegado cuando sus
   filas no se ven. Sin transición a propósito no: acá sí se quiere, es un
   cambio que la persona provoca y espera ver. */
.nav-group-chevron {
  flex: 0 0 auto;
  width: 14px;
  height: 14px;
  transition: transform 0.15s ease;
}

.nav-group-toggle[aria-expanded="false"] .nav-group-chevron { transform: rotate(-90deg); }

.nav-group-items { display: flex; flex-direction: column; gap: 0.15rem; }

.sidebar hr {
  margin: 0.5rem 0.8rem;
  opacity: 0.15;
}

/* ── Buttons ──────────────────────────────────────── */
.btn-primary {
  background: var(--ac-navy);
  border-color: var(--ac-navy);
}

.btn-primary:hover {
  background: #24355e;
  border-color: #24355e;
}

.btn-outline-light:hover {
  background: rgba(255,255,255,0.1);
}

/* Global search box in the navbar: centered, bounded so it does not crowd the
   brand or the account controls (it is hidden below md via d-none d-md-flex). */
.navbar .global-search {
  max-width: 22rem;
  flex: 0 1 22rem;
}

/* == UX-24 / UX-25 · la paleta de comandos =================================
 *
 * Un solo componente para las dos filas: `Ctrl/⌘+K` en el escritorio y la lupa
 * de la barra bajo 768 px, donde la búsqueda global desaparecía sin dejar nada
 * en su lugar. En una pantalla angosta el panel ocupa todo el alto, que es lo
 * que `UX-24` pide como "pantalla completa"; en una ancha se apoya arriba, que
 * es donde el ojo ya está cuando uno acaba de apretar el atajo.
 *
 * Los colores salen de los tokens de tema: a diferencia del informe, esto es
 * interfaz y tiene que seguir la preferencia de quien mira. */
.command-palette {
  position: fixed;
  inset: 0;
  z-index: 1080; /* sobre el modal de Bootstrap (1055) y su fondo (1050) */
  display: flex;
  flex-direction: column;
  align-items: center;
}
.command-palette-backdrop {
  position: absolute;
  inset: 0;
  background: rgb(6 12 24 / 55%);
}
.command-palette-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  /* Estos cuatro son geometría del componente y no escala: no hay paso de
     `--sp-*` que signifique "el ancho de una paleta". Los de tipografía y
     espaciado interior sí salen de los tokens (`UX-02`). */
  width: min(38rem, 100%);
  max-height: min(60vh, 32rem);
  margin-top: 12vh;
  overflow: hidden;
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  border: 1px solid var(--ac-border);
  border-radius: var(--sp-2);
  box-shadow: 0 18px 48px rgb(6 12 24 / 35%);
}
.command-palette-input {
  flex: none;
  padding: var(--sp-4) var(--sp-5);
  border: 0;
  border-bottom: 1px solid var(--ac-border);
  background: transparent;
  color: inherit;
  font-size: var(--fs-base);
}
.command-palette-input:focus { outline: none; }
.command-palette-results {
  flex: 1 1 auto;
  margin: 0;
  padding: var(--sp-2);
  overflow-y: auto;
}
.command-palette-item {
  display: block;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--sp-1);
  color: inherit;
  text-decoration: none;
}
/* `is-active` la pone el teclado **y** el puntero, para que no puedan discrepar:
 * mover el ratón sobre la lista y apretar Enter no debe abrir otra cosa que la
 * resaltada. */
.command-palette-item.is-active { background: var(--ac-surface); }
.command-palette-item.is-search { font-weight: 600; }
.command-palette-empty { padding: var(--sp-4); color: var(--ac-text-muted); }
.command-palette-foot {
  flex: none;
  padding: var(--sp-3) var(--sp-5);
  border-top: 1px solid var(--ac-border);
  color: var(--ac-text-muted);
}
/* El fondo no se desplaza mientras la paleta está abierta: en un teléfono, con
 * el panel a pantalla completa, el deslizamiento se lo lleva la página de atrás
 * y la lista de resultados no se mueve. */
body.palette-open { overflow: hidden; }

@media (max-width: 767.98px) {
  .command-palette-panel {
    width: 100%;
    max-height: 100%;
    height: 100%;
    margin-top: 0;
    border: 0;
    border-radius: 0;
  }
}

/* Scoped to .navbar so it outranks the generic `.form-select` control height
   further down the file. With a single class it lost on source order: the
   31px asked for here silently became the 42px of --ac-control-height, which
   is why the language box stood 8px taller than the buttons beside it. */
.navbar .language-switch {
  width: 3.7rem;
  height: 34px;
  min-height: 34px;
  padding-block: 0;
  background-color: transparent;
  border-color: rgba(255,255,255,0.65);
  color: #fff;
  font-weight: 600;
  /* Bootstrap reserves 36px of right padding for the caret against 8px on the
     left, so `text-align: center` centred the text inside a 13px content box
     shoved 14px off-centre. For a two-letter switch the caret costs more than
     it explains, so this one control drops it and pads evenly instead. Every
     other <select> keeps its caret. */
  background-image: none;
  padding-inline: 0.4rem;
  text-align: center;
  text-align-last: center;
}

.language-switch option {
  color: #243247;
}

/* ── Cards / Summary ──────────────────────────────── */
/* The real .summary-card/.card rules live further down (--ac-* token
   generation); this block used to duplicate them with hardcoded colors
   (--ac-navy background, #fff text) that the newer rules don't override,
   leaving white text with no matching background. Only .card's border/shadow/
   radius survive here -- .summary-card's actual styling comes from the block
   near the bottom of this file. */
.card {
  border: none;
  box-shadow: 0 1px 4px rgba(0,0,0,0.06);
  border-radius: 10px;
}

.skip-link {
  position: fixed;
  top: 0.5rem;
  left: 0.5rem;
  z-index: 2000;
  padding: 0.55rem 0.8rem;
  color: #fff;
  background: var(--ac-navy);
  border-radius: 0.35rem;
  transform: translateY(-150%);
}

.skip-link:focus { transform: translateY(0); }

:where(a, button, input, select, textarea):focus-visible {
  outline: 3px solid var(--ac-turquoise);
  outline-offset: 2px;
}

.onboarding-card {
  border: 1px solid rgba(46, 196, 182, 0.3);
  background: linear-gradient(135deg, rgba(46, 196, 182, 0.08), transparent 55%);
}

.onboarding-steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.75rem 2rem;
  padding-left: 1.35rem;
}

@media (max-width: 768px) {
  .onboarding-steps { grid-template-columns: 1fr; }
}

/* ── Tables ───────────────────────────────────────── */
.table th {
  font-weight: 600;
  color: #555;
  border-top: none;
}

.table-hover tbody tr:hover {
  background: #f0faf9;
}

/* ── Pagination ───────────────────────────────────── */
.pagination .page-link {
  color: var(--ac-navy);
  border: 1px solid #dee2e6;
}

.pagination .page-item.active .page-link {
  background: var(--ac-navy);
  border-color: var(--ac-navy);
}

/* ── Kanban Board ─────────────────────────────────── */
.kanban-board {
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  padding-bottom: 1rem;
  min-height: 70vh;
  align-items: flex-start;
}

.kanban-column {
  background: #f1f3f5;
  border-radius: 10px;
  min-width: 300px;
  max-width: 340px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
}

.kanban-column-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.75rem 1rem;
  font-weight: 600;
  font-size: 0.92rem;
  border-top: 3px solid var(--ac-turquoise);
  border-radius: 10px 10px 0 0;
  background: #fff;
}

.kanban-header-counts {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.kanban-count {
  background: #dee2e6;
  color: #555;
  font-size: 0.78rem;
  padding: 0.15rem 0.5rem;
  border-radius: 10px;
  font-weight: 600;
}

/* B3.4: same shape as .kanban-count so it reads as a sibling counter, not a
   new widget -- colour marks it as the urgent one, but the icon + aria-label
   (not colour alone) carry the "overdue" meaning for anyone who can't see it.
   Compound selector (not just .kanban-count-overdue) on purpose: the plain
   .kanban-count background/color get redeclared later in this file (the
   token-consolidation pass around .kanban-column), and an equal-specificity
   single-class rule would lose that cascade fight by source order. */
.kanban-count.kanban-count-overdue {
  background: var(--ac-danger);
  color: #fff;
}
[data-theme="dark"] .kanban-count.kanban-count-overdue,
[data-bs-theme="dark"] .kanban-count.kanban-count-overdue { background: #ff8a80; color: #2a0a0a; }

/* B3.5: same compound-selector reasoning as .kanban-count-overdue above --
   plain .kanban-count would lose the cascade fight against the later
   token-consolidation rule. Amber, not red: this is a soft warning, the
   drop is never blocked. */
.kanban-count.kanban-count-wip-exceeded {
  background: #f59e0b;
  color: #2a1400;
}
[data-theme="dark"] .kanban-count.kanban-count-wip-exceeded,
[data-bs-theme="dark"] .kanban-count.kanban-count-wip-exceeded { background: #fbbf24; color: #2a1400; }

.kanban-cards {
  padding: 0.5rem;
  flex: 1;
  min-height: 60px;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.kanban-card {
  background: #fff;
  border-radius: 8px;
  padding: 0.7rem 0.9rem;
  box-shadow: 0 1px 3px rgba(0,0,0,0.08);
  cursor: grab;
  transition: box-shadow 0.15s ease, transform 0.15s ease;
  border-left: 3px solid transparent;
}

.kanban-card.priority-low { border-left-color: #95a5a6; }
.kanban-card.priority-medium { border-left-color: #3498db; }
.kanban-card.priority-high { border-left-color: #f39c12; }
.kanban-card.priority-critical { border-left-color: #e74c3c; }

.kanban-card:hover {
  box-shadow: 0 3px 8px rgba(0,0,0,0.12);
  transform: translateY(-1px);
}

.kanban-card:active {
  cursor: grabbing;
}

.kanban-card-title {
  font-weight: 600;
  font-size: 0.85rem;
  margin-bottom: 0.35rem;
  color: var(--ac-navy);
  word-break: break-word;
}

.kanban-card-meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.kanban-priority {
  /* Floor of 0.76rem (12.2px) for metadata. At 0.7rem these badges were 11.2px,
     and no contrast ratio rescues 11px uppercase text. */
  font-size: 0.78rem;
  font-weight: 600;
  padding: 0.1rem 0.5rem;
  border-radius: 4px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  line-height: 1.4;
}

.priority-low {
  background: #e9ecef;
  /* #6c757d on this grey measured 3.95:1. The other three priority badges sit
     between 5.9 and 8.9:1, so "Baja" was the odd one out. */
  color: #495057;
  border: 1px solid #dee2e6;
}

.priority-medium {
  background: #cfe2ff;
  color: #084298;
}

.priority-high {
  background: #ffe5c4;
  color: #984c0c;
}

.priority-critical {
  background: #f8d7da;
  color: #842029;
}

.kanban-assignee {
  font-size: 0.78rem;
  color: #6c757d;
}

.kanban-due {
  font-size: 0.78rem;
  color: #adb5bd;
  margin-top: 0.2rem;
}

.kanban-empty {
  color: #adb5bd;
  font-size: 0.85rem;
  text-align: center;
  padding: 1.5rem 0;
}

.kanban-footer {
  padding: 0.5rem;
  border-top: 1px solid #e9ecef;
}

.kanban-ghost {
  opacity: 0.4;
  background: #dee2e6 !important;
}

.sortable-chosen {
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
}

.kanban-filter {
  width: auto;
  min-width: 140px;
}

/* ── Responsive ───────────────────────────────────── */
@media (max-width: 768px) {
  .sidebar {
    position: fixed;
    /* 58px: the navbar's real height, and the value the (now removed)
       duplicate block was actually rendering. */
    top: 58px;
    bottom: 0;
    left: 0;
    z-index: 1040;
    width: min(86vw, var(--ac-sidebar-width));
    overflow-y: auto;
    transform: translateX(-105%);
    /* transition lives in the base .sidebar rule now (LV-63) -- it already
       includes transform, and duplicating it here is exactly the trap this
       fix removes elsewhere. */
  }
  .sidebar.is-open { transform: translateX(0); }
  .sidebar-toggle { display: inline-flex; align-items: center; justify-content: center; }
  .brand-subtitle { display: none; }
  .navbar > .d-flex { margin-left: auto; }
  .navbar > .d-flex > .opacity-75 { display: none; }
  main { min-width: 0; }
  .table-responsive { border-radius: 0.5rem; }
  .modal-dialog { margin: 0.5rem; }
  .modal-body { max-height: calc(100vh - 10rem); overflow-y: auto; }
}

@media (min-width: 769px) {
  .sidebar-toggle { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Dark brand overrides only. The surface/text/border values that used to live
   here were a second, near-identical set (#1a1f2e vs #161f2d, #20283a vs
   #1b2737, #8f9caf vs #8d9bb0) that produced five slightly different dark
   surfaces on one screen. They now come from the block at the end of the file,
   which already won on source order for every shared selector. */
[data-theme="dark"] {
  --ac-navy: #0d1520;
  --ac-turquoise: #2EC4B6;
}

[data-theme="dark"] .sidebar {
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}

/* The link, hover and active colours that used to sit here were overridden by
   the same selectors at the end of the file, so editing them changed nothing. */
[data-theme="dark"] .sidebar-label {
  /* #6b7a8f measured 3.79:1 against the dark sidebar at 10.9px, under the
     4.5:1 AA floor; this brighter grey clears it. */
  color: #a9b6c9;
}

/* LV-207: los mismos ocho, en su variante para fondo oscuro. Ratios calculados
   contra `--ac-surface` del tema oscuro (`#161f2d`): entre 7.6 y 11.8, todos
   holgados. El orden repite el de arriba a propósito, para que se lea de un
   golpe que las dos listas cubren las mismas ocho secciones. */
[data-theme="dark"] .nav-overview .nav-icon { color: #6ed4e3; }
[data-theme="dark"] .nav-flight .nav-icon { color: #86efac; }
[data-theme="dark"] .nav-compliance .nav-icon { color: #ffc66d; }
[data-theme="dark"] .nav-reports .nav-icon { color: #f2a1c2; }
[data-theme="dark"] .nav-maintenance .nav-icon { color: #c4a5ff; }
[data-theme="dark"] .nav-registry .nav-icon { color: #82b1ff; }
[data-theme="dark"] .nav-inventory .nav-icon { color: #55d6c8; }
[data-theme="dark"] .nav-admin .nav-icon { color: #b9c4d2; }

[data-theme="dark"] .card {
  --bs-card-bg: var(--ac-surface);
  --bs-card-color: var(--ac-text);
  --bs-card-border-color: var(--ac-border);
  background: var(--ac-surface);
  color: var(--ac-text);
  box-shadow: 0 1px 4px rgba(0,0,0,0.3);
}

/* The three --bs-table-* declarations that used to sit here are set again at the
   end of the file; only the hover pair is unique to this rule. */
[data-theme="dark"] .table {
  --bs-table-hover-color: var(--ac-text);
  --bs-table-hover-bg: #263246;
  color: var(--ac-text);
  background-color: var(--ac-surface);
}

[data-theme="dark"] .table th {
  color: var(--ac-text-secondary);
}

[data-theme="dark"] .table-light {
  --bs-table-color: var(--ac-text);
  --bs-table-bg: var(--ac-surface-raised);
  --bs-table-border-color: var(--ac-border);
}

/* The control colours are set at the end of the file; this rule only kept the
   placeholder opacity, which nothing else declares.

   `background-color`, never the `background` shorthand: the shorthand resets
   background-image, and that is what erased Bootstrap's caret from every
   <select> in the application -- the dropdowns stopped looking like dropdowns
   while keeping the 36px of padding reserved for the missing arrow. */
[data-theme="dark"] .form-control::placeholder {
  opacity: 1;
}

[data-theme="dark"] .form-control:focus,
[data-theme="dark"] .form-select:focus {
  background-color: var(--ac-surface-raised);
  border-color: var(--ac-turquoise);
  color: var(--ac-text);
  box-shadow: 0 0 0 0.2rem rgba(46, 196, 182, 0.2);
}

/* The application form controls use the dark palette. Keep modal surfaces in
   the same palette so labels never inherit light text on a white panel. */
[data-theme="dark"] .modal-content {
  --bs-modal-bg: var(--ac-surface-raised);
  --bs-modal-color: var(--ac-text);
  --bs-modal-border-color: var(--ac-border);
  --bs-modal-header-border-color: var(--ac-border);
  --bs-modal-footer-border-color: var(--ac-border);
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  border-color: var(--ac-border);
}

[data-theme="dark"] .modal-title,
[data-theme="dark"] .modal .form-label,
[data-theme="dark"] .modal legend {
  color: var(--ac-text);
}

[data-theme="dark"] .modal .form-text,
[data-theme="dark"] .modal .form-text.text-muted {
  color: var(--ac-text-muted) !important;
}

[data-theme="dark"] .modal .btn-close {
  filter: invert(1) grayscale(100%) brightness(200%);
}

[data-theme="dark"] .modal .form-control::file-selector-button {
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  border-color: var(--ac-border);
}

[data-theme="dark"] .modal .form-control::file-selector-button:hover {
  background: #263246;
}

[data-theme="dark"] .page-link {
  background: #1a1f2e;
  border-color: #2a3040;
  color: #e1e7ef;
}

[data-theme="dark"] .page-item.active .page-link {
  background: #2EC4B6;
  border-color: #2EC4B6;
  color: #1a1f2e;
}

[data-theme="dark"] .page-item.disabled .page-link {
  background: #1a1f2e;
  border-color: #2a3040;
  color: #5a6a7f;
}

[data-theme="dark"] .text-muted {
  color: var(--ac-text-muted) !important;
}

[data-theme="dark"] .bg-light {
  background-color: var(--ac-surface) !important;
}

[data-theme="dark"] .table .btn-outline-secondary {
  color: var(--ac-text);
  border-color: var(--ac-border);
}

[data-theme="dark"] .kanban-column {
  background: var(--ac-surface);
}

[data-theme="dark"] .kanban-column-header {
  background: var(--ac-surface);
  color: var(--ac-text);
}

[data-theme="dark"] .kanban-count {
  background: var(--ac-surface-raised);
  color: var(--ac-text-secondary);
}

[data-theme="dark"] .kanban-card {
  background: #121925;
  box-shadow: 0 1px 3px rgba(0,0,0,0.3);
}

[data-theme="dark"] .kanban-card-title {
  color: var(--ac-text);
}

[data-theme="dark"] .kanban-card:hover {
  box-shadow: 0 3px 8px rgba(0,0,0,0.5);
}

[data-theme="dark"] .priority-low {
  background: var(--ac-surface-raised);
  color: var(--ac-text-secondary);
  border-color: var(--ac-border);
}

[data-theme="dark"] .priority-medium {
  background: #0a2a4a;
  color: #8bb9fe;
}

[data-theme="dark"] .priority-high {
  background: #3a200a;
  color: #ffb776;
}

[data-theme="dark"] .priority-critical {
  background: #3a0a0a;
  color: #ff8a8a;
}

[data-theme="dark"] .kanban-empty {
  color: var(--ac-text-muted);
}

[data-theme="dark"] .kanban-footer {
  border-top-color: var(--ac-border);
}

body, .card, .form-control, .form-select, .page-link, .table,
.sidebar a, .navbar, .badge, .kanban-column, .kanban-card, .kanban-column-header {
  transition: background 0.2s ease, color 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease;
}

/* ── Chart containers ────────────────────────────── */
.chart-container canvas {
  max-height: 220px;
  width: 100% !important;
}

[data-theme="dark"] .chart-container {
  background: #1a1f2e;
}

/* ── UI foundation refinement ─────────────────────── */
/* The single source of design tokens. Light values here, dark overrides in the
   [data-theme="dark"] block below. */
:root {
  /* LV-170: 248 px cortaba **cuatro** filas del menú. Medido en el navegador con
     esta misma hoja: la más larga, "Evaluación de conocimientos", pide 248 px
     ella sola, y la barra incluye sus 24 px de relleno — así que el mínimo real
     son 272. Los 8 de más son colchón para que una métrica de fuente apenas
     distinta no vuelva a recortar la última letra, no un número al azar. */
  /* LV-208: el ancho que el usuario haya fijado arrastrando el borde manda, y
     los 280 quedan como valor por defecto.

     **Va en `:root` y no en `.sidebar`, y eso no es indiferente.** Una custom
     property se resuelve en el elemento donde se *usa*, así que si el valor del
     usuario se escribiera inline en `.sidebar` ganaría por especificidad de
     estilo inline y **anularía `--ac-sidebar-width: 72px` del estado colapsado**
     — el botón `<` dejaría de hacer nada en cuanto alguien tocara el borde una
     vez. Escribiéndolo en `documentElement`, la regla `.sidebar.is-collapsed`
     sigue ganando sobre `:root` por ser más específica, y los dos controles
     conviven: el arrastre fija el ancho abierto, el botón sigue colapsando. */
  --ac-sidebar-width: var(--ac-sidebar-width-user, 280px);
  /* == UX-02 · La escala tipográfica y de espaciado, como tokens =============
   *
   * Hoy hay **311 valores `rem` literales** en este archivo, y doce de ellos se
   * repiten entre 9 y 26 veces. El problema no es la repetición: es que
   * `0.78rem` y `0.8rem` conviven sin que nadie pueda decir si son dos
   * decisiones o un dedazo, y que subir "el cuerpo de las tablas" obliga a
   * buscar cuál de las cinco medidas parecidas era.
   *
   * ⚠️ **Los literales NO se migran de una pasada, y eso es la fila.** Un
   * reemplazo masivo de 311 valores es un diff que nadie puede revisar y en el
   * que un `0.85rem` cambiado a `0.8rem` por descuido no lo caza ningún test —
   * este CSS no tiene pruebas de píxel. Se migran **al tocar cada bloque por
   * otra razón**, que es la misma política incremental que el `MASTER_PLAN`
   * aplica a `core/views.py`. Lo que entra ahora es la escala: a partir de acá
   * un valor nuevo usa el token, y el que no exista en la escala es una
   * decisión que hay que justificar en vez de teclear.
   *
   * La escala sale de **lo que el archivo ya usa**, no de una tabla traída de
   * fuera: los pasos son los valores más repetidos, redondeados a los que ya
   * dominan. Inventar una escala nueva habría obligado a mover todo el archivo
   * el día que se migre, que es justo lo que se está evitando. */
  --fs-xs: 0.72rem;   /* pies de tabla, notas al pie */
  --fs-sm: 0.78rem;   /* el cuerpo de las listas -- 22 usos hoy */
  --fs-md: 0.85rem;   /* texto secundario -- 26 usos hoy */
  --fs-base: 1rem;    /* el cuerpo */
  --fs-lg: 1.25rem;
  --fs-xl: 1.5rem;

  --sp-1: 0.15rem;
  --sp-2: 0.35rem;    /* 19 usos hoy */
  --sp-3: 0.5rem;     /* 26 usos hoy */
  --sp-4: 0.75rem;
  --sp-5: 1rem;
  --sp-6: 1.5rem;

  /* Brand palette. Kept apart from --ac-primary, which is the accessible
     action colour: the brand turquoise does not clear 4.5:1 on white. */
  --ac-navy: #1B2A4A;
  --ac-turquoise: #2EC4B6;
  --ac-bg: #f4f7fb;
  --ac-surface: #ffffff;
  --ac-surface-raised: #ffffff;
  --ac-border: #dbe3ee;
  --ac-text: #172238;
  --ac-text-secondary: #516079;
  /* Was #748198, which measured 3.94:1 on white -- under the 4.5:1 AA floor.
     Because it is a token, every muted label on a light surface failed at once
     ("Navegación", the empty-column notice). #626f85 clears it at 5.09:1 and
     still reads as muted. */
  --ac-text-muted: #626f85;
  --ac-primary: #087f78;
  --ac-primary-hover: #06655f;
  --ac-primary-soft: #d8f5f1;
  /* LV-185: los mismos dos colores en RGB, porque Bootstrap 5.3 arma el color
     de enlace con `rgba(var(--bs-link-color-rgb), …)` y **ignora**
     `--bs-link-color`. Comprobado en el navegador: fijar sólo la forma hex no
     mueve un píxel, que es la peor clase de arreglo — el CSS parece decir algo y
     no dice nada.

     Duplicar un color en dos notaciones es exactamente la deriva que este repo
     ya arrastró con los tokens, así que hay un test que exige que el triplete y
     el hex sean el mismo color. Sin ese guardián esto se desincroniza y nadie
     lo nota hasta que un enlace queda de otro color. */
  --ac-primary-rgb: 8, 127, 120;
  --ac-primary-hover-rgb: 6, 101, 95;
  /* En claro manda el paso **oscuro**: `--ac-primary` da 4.53 sobre el fondo,
     que cumple AA por 0,03 y no deja nada para un monitor distinto; el oscuro
     da 6.44. Medido, no elegido a ojo. El hover invierte el par, que es lo que
     hace que el enlace se aclare al pasar por encima. */
  --bs-link-color-rgb: var(--ac-primary-hover-rgb);
  --bs-link-hover-color-rgb: var(--ac-primary-rgb);
  --ac-danger: #c53b4d;
  --ac-shadow: 0 10px 30px rgba(18, 34, 59, 0.08);
  --ac-radius: 12px;
  --ac-control-height: 42px;
}

html { color-scheme: light; }

body {
  background: var(--ac-bg);
  color: var(--ac-text);
  font-size: 0.95rem;
  line-height: 1.5;
  letter-spacing: 0.005em;
}

main {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
}

.navbar-dark.navy {
  min-height: 58px;
  background: #0d1727;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.navbar-brand { letter-spacing: -0.01em; }
.sidebar-toggle,
.theme-toggle { min-width: 40px; min-height: 36px; }

.sidebar a {
  min-height: 42px;
  padding: 0.62rem 0.75rem;
  border-radius: 9px;
  color: var(--ac-text-secondary);
  font-size: 0.92rem;
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
}

.sidebar a:hover { background: var(--ac-primary-soft); color: var(--ac-text); }
.sidebar a.active { background: var(--ac-primary-soft); color: var(--ac-text); box-shadow: inset 3px 0 0 var(--ac-primary); }
.sidebar-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; min-height: 34px; margin-bottom: 0.35rem; padding-inline: 0.45rem; }
.sidebar-toolbar-title { color: var(--ac-text-secondary); font-size: 0.78rem; font-weight: 800; letter-spacing: 0.1em; text-transform: uppercase; }
.sidebar-collapse { display: inline-grid; place-items: center; width: 32px; height: 32px; padding: 0; border: 1px solid var(--ac-border); border-radius: 8px; background: var(--ac-surface-raised); color: var(--ac-text-secondary); }
.sidebar-collapse:hover, .sidebar-collapse:focus-visible { color: var(--ac-text); border-color: var(--ac-primary); background: var(--ac-primary-soft); }
/* Group headings: --ac-text-secondary only reached 3.79:1 at this size,
   below the 4.5:1 AA floor. A brighter token plus a hairline rule above each
   group makes the sections readable and visually separated. */
.sidebar-label {
  color: var(--ac-text);
  margin-top: 1.1rem;
  padding-block: 0.55rem 0.35rem;
  padding-inline: 0.45rem;
  border-top: 1px solid var(--ac-border);
  font-size: 0.78rem;
  font-weight: 800;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  opacity: 0.85;
}
.sidebar-label:first-of-type { border-top: 0; margin-top: 0.4rem; }
.sidebar hr { border-color: var(--ac-border); opacity: 1; }

/* LV-63: the collapsed rail is a desktop-only mode -- app.js only ever
   toggles is-collapsed when innerWidth >= 769 (it is the sidebar-collapse
   button's whole job; there is no way to enter it from a narrow viewport).
   Scoping it here means a class left over from resizing down out of desktop
   simply does nothing on mobile, instead of needing separate mobile
   counter-rules to undo it (the two blocks further down that used to do
   exactly that, and did it wrong: the sidebar-width var was already 72px by
   the time they ran, so "un-collapsing" resolved to min(86vw, 72px) instead
   of the full drawer width). */
@media (min-width: 769px) {
  .sidebar.is-collapsed {
    --ac-sidebar-width: 72px;
    padding-inline: 0.75rem !important;
  }

  .sidebar.is-collapsed .sidebar-label,
  .sidebar.is-collapsed hr { display: none; }
  .sidebar.is-collapsed .sidebar-toolbar { justify-content: center; padding-inline: 0; }
  .sidebar.is-collapsed .sidebar-toolbar-title { display: none; }
  .sidebar.is-collapsed .sidebar-collapse svg { transform: rotate(180deg); }
  .sidebar.is-collapsed .nav-item { position: relative; justify-content: center; padding-inline: 0; font-size: 0; }
  .sidebar.is-collapsed .nav-icon { margin: 0; }
  /* LV-170: con la barra en modo icono el rótulo del grupo no se dibuja (regla
     de arriba), así que no habría con qué volver a desplegarlo y las filas de un
     grupo plegado quedarían **inalcanzables**. En ese modo los grupos se
     muestran siempre; el estado plegado se conserva y vuelve al expandir la
     barra. Gana al `hidden` del JS porque `[hidden]` es `display:none` de la
     hoja del navegador y esto es más específico. */
  .sidebar.is-collapsed .nav-group-items[hidden] { display: flex; }

  /* Keep the pending-alert count visible while collapsed: it is the one piece
     of live state in the nav, so hiding it removed the reason to glance at
     it. Rendered as a compact dot-badge pinned to the icon. */
  .sidebar.is-collapsed .nav-item .badge {
    position: absolute;
    top: 4px;
    right: 8px;
    min-width: 17px;
    height: 17px;
    padding: 0 4px;
    border-radius: 9px;
    border: 2px solid var(--ac-surface);
    font-size: 0.62rem;
    font-weight: 700;
    line-height: 13px;
    text-align: center;
  }
  .sidebar.is-collapsed .nav-item .badge:empty { display: none; }
}

.page-header,
.toolbar,
.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.page-header { margin-bottom: 1.25rem; }
.page-header h1 { margin: 0; font-size: clamp(1.65rem, 2vw, 2.2rem); font-weight: 700; letter-spacing: -0.035em; }
/* The darker step of the same teal: --ac-primary sits at 4.53:1 on white, which
   clears AA by 0.03 and leaves nothing for a slightly different monitor. */
.eyebrow { color: var(--ac-primary-hover); font-size: 0.78rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; }
.page-eyebrow { display: block; margin-bottom: 0.25rem; color: var(--ac-primary); font-size: 0.72rem; font-weight: 800; letter-spacing: 0.12em; text-transform: uppercase; }
.page-description { margin: 0.35rem 0 0; color: var(--ac-text-secondary); }

.btn {
  min-height: 40px;
  border-radius: 8px;
  font-weight: 700;
  letter-spacing: 0.005em;
}

.btn-sm { min-height: 34px; }
.btn-primary { background: var(--ac-primary); border-color: var(--ac-primary); color: #fff; }
.btn-primary:hover, .btn-primary:focus-visible { background: var(--ac-primary-hover); border-color: var(--ac-primary-hover); color: #fff; }
.btn-outline-primary { color: var(--ac-primary); border-color: var(--ac-primary); }
.btn-outline-primary:hover, .btn-outline-primary:focus-visible { background: var(--ac-primary); border-color: var(--ac-primary); color: #fff; }
.btn-outline-secondary { color: var(--ac-text-secondary); border-color: var(--ac-border); }
.btn-outline-secondary:hover, .btn-outline-secondary:focus-visible { background: var(--ac-surface-raised); border-color: var(--ac-primary); color: var(--ac-text); }

.card,
.panel,
.modal-content,
.offcanvas {
  background: var(--ac-surface);
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius);
  box-shadow: var(--ac-shadow);
}

.card { overflow: hidden; }
.card.p-4, .card.p-5 { padding: clamp(1.15rem, 2.4vw, 2rem) !important; }

.form-label,
.form-check-label { color: var(--ac-text); font-weight: 700; }
.form-control,
.form-select {
  min-height: var(--ac-control-height);
  border: 1px solid var(--ac-border);
  border-radius: 8px;
  /* background-color, not the `background` shorthand: the shorthand resets
     background-image, which is where Bootstrap draws the <select> caret. */
  background-color: var(--ac-surface-raised);
  color: var(--ac-text);
}
.form-control:focus, .form-select:focus { border-color: var(--ac-primary); box-shadow: 0 0 0 0.2rem rgba(8, 127, 120, 0.15); }
.form-text, .invalid-feedback { color: var(--ac-text-secondary); }
.requiredField .asteriskField { color: var(--ac-danger); }

.table { --bs-table-color: var(--ac-text); --bs-table-bg: var(--ac-surface); --bs-table-border-color: var(--ac-border); }
/* Headers stay on one line and centre with the data. Two problems came from
   letting them wrap: a two-line header ("Tipo de entidad") next to one-line
   neighbours, and Bootstrap aligning headers to the bottom while cells are
   centred, so the header row never sat on the same axis as the rows. The table
   already lives inside .table-responsive, so a wide header scrolls rather than
   breaking the layout. */
.table th {
  color: var(--ac-text-secondary);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}
.table td { vertical-align: middle; }
.table-primary-value { color: var(--ac-text); font-weight: 700; }

.summary-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.85rem; }
.summary-card { display: flex; flex-direction: column; gap: 0.15rem; padding: 0.65rem 0.85rem; border: 1px solid var(--ac-border); border-radius: var(--ac-radius); background: var(--ac-surface); box-shadow: var(--ac-shadow); }
.summary-card span { color: var(--ac-text-secondary); font-size: 0.78rem; font-weight: 700; }
.summary-card strong { color: var(--ac-primary); font-size: 1.65rem; line-height: 1; }
.summary-card-warning strong { color: #d97706; }

.empty-state {
  display: grid;
  place-items: center;
  min-height: 220px;
  padding: 2rem;
  color: var(--ac-text-secondary);
  text-align: center;
}
.empty-state h2, .empty-state h3 { color: var(--ac-text); }

.status-pill,
.kanban-status,
.kanban-label,
.kanban-priority { border-radius: 999px; }

/* Stage badges: the .status-* classes were used by the Kanban templates but
   never defined, so stage state rendered as bare text. One shared chip shape
   plus a per-state accent keeps the six DGAC stages distinguishable, and the
   left border on the card body means state is not signalled by colour alone. */
.kanban-status {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border: 1px solid var(--stage-color, var(--ac-border));
  background: color-mix(in srgb, var(--stage-color, var(--ac-border)) 18%, transparent);
  color: var(--ac-text);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
}
.status-pending { --stage-color: #8d9bb0; }
.status-planned { --stage-color: #4c9ce6; }
.status-in_progress { --stage-color: #e0a34b; }
.status-blocked { --stage-color: #e2635c; }
.status-review { --stage-color: #a184f0; }
.status-completed { --stage-color: #34c98b; }
.status-custom { --stage-color: var(--ac-primary); }

/* Overdue emphasis: .is-overdue was referenced by the card template with no
   styling, so an overdue task looked identical to any other. */
/* #ff8a80 is a red tuned for a dark surface; on the white card of light mode it
   measured 2.28:1, the worst contrast in the application, on the one label that
   must not be missed. The colour now follows the theme. */
.kanban-due.is-overdue {
  color: var(--ac-danger);
  font-weight: 700;
}
[data-theme="dark"] .kanban-due.is-overdue,
[data-bs-theme="dark"] .kanban-due.is-overdue { color: #ff8a80; }
.kanban-due.is-overdue::before { content: "\26A0\FE0E"; margin-right: 0.3rem; }

/* The toggle now shows the theme it switches *to*, matching its own title and
   aria-label ("Use light theme" while dark). A rotating moon said nothing about
   which theme was active. Keyed off the root attribute, not aria-pressed, so
   the flash-prevention script in <head> paints the right icon before first
   paint instead of correcting it afterwards. */
.theme-toggle { display: inline-flex; align-items: center; justify-content: center; }
/* Inline SVGs sit on the text baseline, which pushed the icon ~2px below the
   centre of the button while every sibling was centred. */
/* All four rules carry `.theme-toggle` so they compete at the same specificity
   and source order decides, which is the only thing that makes the swap
   predictable. */
.theme-toggle .theme-icon { display: block; }
.theme-toggle .theme-icon-sun { display: none; }
[data-theme="dark"] .theme-toggle .theme-icon-moon,
[data-bs-theme="dark"] .theme-toggle .theme-icon-moon { display: none; }
[data-theme="dark"] .theme-toggle .theme-icon-sun,
[data-bs-theme="dark"] .theme-toggle .theme-icon-sun { display: block; }

[data-theme="dark"], [data-bs-theme="dark"] {
  color-scheme: dark;
  --ac-bg: #0d131d;
  --ac-surface: #161f2d;
  --ac-surface-raised: #1b2737;
  --ac-border: #33435b;
  --ac-text: #edf3fb;
  --ac-text-secondary: #b5c1d1;
  --ac-text-muted: #8d9bb0;
  --ac-primary: #42d4c6;
  --ac-primary-hover: #6be0d5;
  --ac-primary-soft: #193a43;
  /* LV-185: en oscuro el par se invierte respecto al claro — acá el turquesa
     base ya da 8.24 sobre la superficie más clara, así que el enlace usa ése y
     el hover **aclara**. En claro no se podía: ahí el base roza el mínimo. */
  --ac-primary-rgb: 66, 212, 198;
  --ac-primary-hover-rgb: 107, 224, 213;
  --bs-link-color-rgb: var(--ac-primary-rgb);
  --bs-link-hover-color-rgb: var(--ac-primary-hover-rgb);
  --ac-danger: #ff7b8b;
  --ac-shadow: 0 12px 32px rgba(0, 0, 0, 0.24);
}

[data-theme="dark"] body,
[data-bs-theme="dark"] body { background: var(--ac-bg); color: var(--ac-text); }
[data-theme="dark"] .navbar-dark.navy,
[data-bs-theme="dark"] .navbar-dark.navy { background: #09111d; }
[data-theme="dark"] .sidebar,
[data-bs-theme="dark"] .sidebar { background: var(--ac-surface); border-color: var(--ac-border); }
[data-theme="dark"] .sidebar a,
[data-bs-theme="dark"] .sidebar a { color: var(--ac-text-secondary); }
[data-theme="dark"] .sidebar-toolbar-title,
[data-bs-theme="dark"] .sidebar-toolbar-title { color: var(--ac-text-secondary); }
[data-theme="dark"] .sidebar a:hover,
[data-theme="dark"] .sidebar a.active,
[data-bs-theme="dark"] .sidebar a:hover,
[data-bs-theme="dark"] .sidebar a.active { background: var(--ac-primary-soft); color: var(--ac-text); }
[data-theme="dark"] .btn-primary,
[data-bs-theme="dark"] .btn-primary { color: #092b2a; }
[data-theme="dark"] .btn-outline-secondary,
[data-bs-theme="dark"] .btn-outline-secondary { color: var(--ac-text-secondary); border-color: var(--ac-border); }
[data-theme="dark"] .form-control,
[data-theme="dark"] .form-select,
[data-bs-theme="dark"] .form-control,
[data-bs-theme="dark"] .form-select { background-color: var(--ac-surface-raised); border-color: var(--ac-border); color: var(--ac-text); }
[data-theme="dark"] .form-control::placeholder,
[data-bs-theme="dark"] .form-control::placeholder { color: var(--ac-text-muted); }
[data-theme="dark"] .table,
[data-bs-theme="dark"] .table { --bs-table-color: var(--ac-text); --bs-table-bg: var(--ac-surface); --bs-table-border-color: var(--ac-border); }
[data-theme="dark"] .table-hover tbody tr:hover,
[data-bs-theme="dark"] .table-hover tbody tr:hover { background: #213247; }
[data-theme="dark"] .modal-header,
[data-theme="dark"] .offcanvas-header,
[data-bs-theme="dark"] .modal-header,
[data-bs-theme="dark"] .offcanvas-header { border-color: var(--ac-border); }

/* LV-63: this used to also carry counter-rules un-collapsing the sidebar on
   mobile ("is-collapsed doesn't apply here, so undo its effects"). Scoping
   .sidebar.is-collapsed itself to min-width:769px (above) made those
   unnecessary -- there is nothing left to undo, so removed rather than kept
   as dead weight. What is left here is page-level mobile spacing, unrelated
   to the sidebar fix. */
@media (max-width: 768px) {
  /* #main-content, not `main`: main.container-fluid already carries
     Bootstrap's .p-4 (1.5rem, !important), which has higher specificity
     (0,1,1) than a bare element selector (0,0,1) -- an !important on the
     losing side of that comparison is still losing, so this rule did
     nothing on any viewport. The id (1,0,0) wins outright. */
  #main-content { padding: 1rem !important; }
  .page-header { align-items: flex-start; }
  .summary-grid { grid-template-columns: 1fr; }
}

/* ── LV-D1: sistema de etiquetas legible (oscuro y claro) ──────────────────
   Los chips y etiquetas con paleta *subtle* tenían el fondo casi idéntico al
   del panel y sin borde, así que no se leían como pastillas (revisión en vivo
   2026-08-03). Se les da un contorno en el tono de su propio texto y un poco
   más de cuerpo tipográfico: la pastilla se define en cualquier superficie sin
   cambiar el color semántico. */
.badge {
  font-weight: 600;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  padding: 0.36em 0.62em;
  border: 1px solid transparent;
  vertical-align: baseline;
}
/* LV-D10: own pill palette. Bootstrap's *-subtle utilities ship with !important
   and colours that do NOT follow our data-theme, so contrast failed in one mode
   or the other. Each family gets an explicit fill/border/text per theme, all
   picked for AA text-on-fill contrast, overriding Bootstrap with a two-class
   selector + !important. The 1px transparent border from .badge above is what
   these border-colours paint. */
.badge[class*="-subtle"] {
  font-weight: 700;
}
/* Light theme */
.badge.bg-primary-subtle   { background-color: #e7efff !important; border-color: #a9c7f5; color: #1b4f9c !important; }
.badge.bg-success-subtle   { background-color: #e4f6ea !important; border-color: #a7d9b8; color: #1f7a43 !important; }
.badge.bg-info-subtle      { background-color: #e0f4f8 !important; border-color: #9fd6e2; color: #14707f !important; }
.badge.bg-warning-subtle   { background-color: #fdf1dc !important; border-color: #ecc98a; color: #8a5a12 !important; }
.badge.bg-danger-subtle    { background-color: #fce4e6 !important; border-color: #f0b0b6; color: #a52834 !important; }
.badge.bg-secondary-subtle { background-color: #eceff4 !important; border-color: #c4ccd8; color: #384252 !important; }
.badge.bg-dark-subtle      { background-color: #e9ebee !important; border-color: #c2c7cf; color: #343a42 !important; }
/* Dark theme */
[data-theme="dark"] .badge.bg-primary-subtle   { background-color: #17304d !important; border-color: #2f5c93; color: #9cc3ff !important; }
[data-theme="dark"] .badge.bg-success-subtle   { background-color: #14361f !important; border-color: #2c6b40; color: #7fd6a0 !important; }
[data-theme="dark"] .badge.bg-info-subtle      { background-color: #123a42 !important; border-color: #2a6b78; color: #79d6e6 !important; }
[data-theme="dark"] .badge.bg-warning-subtle   { background-color: #3d2f14 !important; border-color: #7a5f26; color: #e6c079 !important; }
[data-theme="dark"] .badge.bg-danger-subtle    { background-color: #3d1a1e !important; border-color: #7a343c; color: #f0a0a8 !important; }
/* LV-42: los grises neutros se fundían con la tarjeta oscura (fondo 1.38:1,
   borde 2.2:1 vs card). Se sube el relleno y se aclara el borde a ≥3:1 para que
   la pastilla lea como pastilla; el texto ya contrastaba (~9:1). */
[data-theme="dark"] .badge.bg-secondary-subtle { background-color: #37455c !important; border-color: #657793 !important; color: #e6edf6 !important; }
[data-theme="dark"] .badge.bg-dark-subtle      { background-color: #353d48 !important; border-color: #636d7b !important; color: #e2e7ee !important; }
/* The sidebar alert count keeps its own compact sizing (set earlier); don't let
   the generic bump enlarge it. */
.sidebar a .badge { font-size: 0.72rem; padding: 0.2em 0.5em; }

/* ── UX-01: la severidad como token, en cinco niveles ──────────────────────
   Del plan UX (`docs/ux-ui-plan.md` §6): hoy la severidad se escribe con
   utilidades de Bootstrap y **nada garantiza que ámbar signifique lo mismo en la
   tabla de alertas, en el panel y en el informe**. En una app de cumplimiento
   aeronáutico eso no es estética.

   **Los valores no son nuevos: son los de `LV-D10`**, que ya los eligió y midió
   para cumplir AA de texto sobre relleno en los dos temas. Lo que cambia es que
   dejan de estar amarrados al nombre de una utilidad de Bootstrap (`warning`,
   `info`) y pasan a llamarse por lo que significan. Inventar colores nuevos acá
   habría tirado a la basura una medición hecha.

   Los cinco niveles y el tramo de vencimiento al que corresponden — el mapeo lo
   fijó `LV-148` y esto sólo le pone nombre:

   | nivel      | tramo      | qué dice                                  |
   |------------|------------|-------------------------------------------|
   | `critical` | `overdue`  | ya venció; hay incumplimiento hoy         |
   | `warning`  | `due_7`    | vence esta semana                         |
   | `caution`  | `due_15`   | vence en quince días                      |
   | `advisory` | `due_30`   | vence en el mes; hay tiempo de gestionarlo|
   | `nominal`  | `later`    | sin urgencia                              |

   ⚠️ **Esto es severidad y NADA más.** El tono de tipo (`SUBJECT_TONE_CSS`,
   `LV-217`) y el color de sección del menú (`.nav-*`, `LV-207`) contestan otras
   preguntas y **no** se migran acá: un tipo y una severidad compitiendo por el
   mismo canal es cómo se pierde el rojo, que es la única señal que tiene que
   gritar. Por eso ningún nivel de abajo se usa para hablar de otra cosa. */
:root {
  --sev-critical-bg: #fce4e6;
  --sev-critical-border: #f0b0b6;
  --sev-critical-text: #a52834;
  --sev-warning-bg: #fdf1dc;
  --sev-warning-border: #ecc98a;
  --sev-warning-text: #8a5a12;
  /* `caution` comparte familia con `warning` a propósito: los dos son ámbar
     porque los dos dicen "esta quincena". Se distinguen por el peso del texto en
     la fila, no por el matiz — dos ámbares separados por poco serían dos avisos
     que nadie distingue, y ya se midió hoy (`LV-217`) que dos fondos suaves a
     distancia RGB corta son indistinguibles. */
  --sev-caution-bg: #fdf1dc;
  --sev-caution-border: #ecc98a;
  --sev-caution-text: #8a5a12;
  --sev-advisory-bg: #e0f4f8;
  --sev-advisory-border: #9fd6e2;
  --sev-advisory-text: #14707f;
  --sev-nominal-bg: #eceff4;
  --sev-nominal-border: #c4ccd8;
  --sev-nominal-text: #384252;
}

[data-theme="dark"] {
  --sev-critical-bg: #3d1a1e;
  --sev-critical-border: #7a343c;
  --sev-critical-text: #f0a0a8;
  --sev-warning-bg: #3d2f14;
  --sev-warning-border: #7a5f26;
  --sev-warning-text: #e6c079;
  --sev-caution-bg: #3d2f14;
  --sev-caution-border: #7a5f26;
  --sev-caution-text: #e6c079;
  --sev-advisory-bg: #123a42;
  --sev-advisory-border: #2a6b78;
  --sev-advisory-text: #79d6e6;
  /* LV-42: en oscuro los neutros se fundían con la tarjeta, así que este nivel
     lleva relleno más alto y borde más claro que su equivalente en claro. */
  --sev-nominal-bg: #37455c;
  --sev-nominal-border: #657793;
  --sev-nominal-text: #e6edf6;
}

/* == LV-262 · El cierre mensual ============================================
 * Un chip por tipo de registro: sólido con su cantidad si está, punteado y en
 * ámbar si falta — es lo que se viene a buscar, así que se lee sin contar. Los
 * colores son los de severidad, ya calculados para leerse en los dos temas. */
.close-chip {
  display: inline-flex;
  align-items: center;
  padding: 1px 8px;
  border-radius: 999px;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.close-chip-ok {
  background: var(--bs-success-bg-subtle);
  color: var(--bs-success-text-emphasis);
  border: 1px solid var(--bs-success-border-subtle);
}
.close-chip-missing {
  background: transparent;
  color: var(--sev-warning-text);
  border: 1px dashed var(--sev-warning-border);
}
/* Las acciones en **una** línea: medido en el navegador, con `flex-wrap`
   «Cargar» quedaba arriba y la nota con los botones abajo, y cada fila medía el
   doble. Lo que envuelve es la columna de registros, no la de acciones. */
.monthly-close .close-actions {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--sp-1);
  justify-content: flex-end;
  align-items: center;
}
/* Y el texto de cada botón tampoco se parte: sin esto, al no poder envolver la
   fila, el navegador partía «No cumple» y «+ Cargar» en dos renglones — visto en
   la captura, no en la medición, que daba la fila en una línea. */
.monthly-close .close-actions .btn { white-space: nowrap; }
.monthly-close .close-note { width: 9rem; }

/* == LV-259 · Contraste de los colores de Bootstrap que la app usa sueltos ====
 *
 * Medido el 2026-09-25 sobre 16 pantallas, con los dos temas y calculando el
 * contraste contra el fondo efectivo de cada texto. Cuatro familias no llegaban:
 *
 * - **Rojo y verde de Bootstrap en oscuro** (`btn-outline-danger` de «Archivar»,
 *   `text-danger`/`text-success` de los ✓/✕): Bootstrap no los aclara en su tema
 *   oscuro, y `#dc3545`/`#198754` sobre la tarjeta oscura dan **3,66 : 1**. Se
 *   usan los tonos que la app ya tiene para eso: `--sev-critical-text` (8,1) y
 *   el énfasis verde de Bootstrap (7,1).
 * - **Insignias amarillas y celestes con texto blanco** («Solicitado», «En
 *   curso»): blanco sobre `#ffc107` es **1,63 : 1** en los dos temas. Texto
 *   oscuro: 10,2 y 8,5.
 * - **`text-warning` sobre blanco**: el mismo amarillo como letra, 1,63. Pasa al
 *   ámbar de severidad, que en cada tema ya está calculado para leerse.
 *
 * `!important` porque las utilidades de Bootstrap lo traen de fábrica; es la
 * misma pelea que documentan las reglas de arriba. */
.badge.bg-warning,
.badge.bg-info { color: #1b1f24; }
.text-warning { color: var(--sev-warning-text) !important; }
[data-theme="dark"] .text-danger,
[data-bs-theme="dark"] .text-danger { color: var(--sev-critical-text) !important; }
[data-theme="dark"] .text-success,
[data-bs-theme="dark"] .text-success { color: var(--bs-success-text-emphasis, #75b798) !important; }
[data-theme="dark"] .btn-outline-danger,
[data-bs-theme="dark"] .btn-outline-danger {
  --bs-btn-color: var(--sev-critical-text);
  /* El borde del mismo tono que la letra: `--sev-critical-border` es un rojo
     apagado pensado para enmarcar una insignia, y sobre la tarjeta oscura el
     contorno del botón casi desaparecía. */
  --bs-btn-border-color: var(--sev-critical-text);
}
/* LV-262: el verde del mismo problema. No salía en ninguna de las 16 pantallas
   medidas en `LV-259`; lo trajo el botón «Cumple» del cierre mensual, 3,66 : 1. */
[data-theme="dark"] .btn-outline-success,
[data-bs-theme="dark"] .btn-outline-success {
  --bs-btn-color: var(--bs-success-text-emphasis, #75b798);
  --bs-btn-border-color: var(--bs-success-text-emphasis, #75b798);
}

/* Las clases que consumen los tokens. **Sin `!important`, y eso es el punto**:
   las quince reglas de arriba lo necesitan porque pelean contra las utilidades de
   Bootstrap, que vienen con `!important` de fábrica. Una clase propia no compite
   con nadie. */
.badge.sev-critical { background-color: var(--sev-critical-bg); border-color: var(--sev-critical-border); color: var(--sev-critical-text); }
.badge.sev-warning  { background-color: var(--sev-warning-bg);  border-color: var(--sev-warning-border);  color: var(--sev-warning-text); }
.badge.sev-caution  { background-color: var(--sev-caution-bg);  border-color: var(--sev-caution-border);  color: var(--sev-caution-text); }
.badge.sev-advisory { background-color: var(--sev-advisory-bg); border-color: var(--sev-advisory-border); color: var(--sev-advisory-text); }
.badge.sev-nominal  { background-color: var(--sev-nominal-bg);  border-color: var(--sev-nominal-border);  color: var(--sev-nominal-text); }

/* LV-246: la variante de **fila entera**, para la faena que no tiene permiso
   vigente.

   ⚠️ **Existe porque la clase anterior no existía.** La tabla de permisos por faena
   escribía `class="table-warning-subtle"` desde `LV-206` y esa clase **no está ni en
   `app.css` ni en Bootstrap** —que tiene `.table-warning` y las utilidades
   `*-subtle` por separado, no combinadas—, así que la fila que el comentario decía
   destacar nunca se pintó de nada en quince meses. El texto "Ninguno" en ámbar era
   todo lo que había, y por eso las siete faenas incumplidas se leían igual que las
   demás.

   Va sobre `td` y no sobre `tr` porque `<tr>` no acepta fondo cuando las celdas
   traen el suyo, que es de donde viene la mitad de los intentos fallidos de pintar
   filas. `box-shadow` en vez de `border` para no descuadrar la altura de la fila. */
tr.row-critical > td {
  background-color: var(--sev-critical-bg);
  color: var(--sev-critical-text);
}
/* ⚠️ LV-261: **el paso del mouse tapaba el rojo, y la primera celda no se
   enteraba.** `.table-hover` de Bootstrap pinta el realce con una sombra interior
   (`box-shadow: inset 0 0 0 9999px var(--bs-table-bg-state)`), que se dibuja
   **encima** del fondo: la fila crítica se volvía gris azulado justo al señalarla.
   Y la primera celda, que declara su propia sombra para la barra del canto,
   **reemplazaba** la de Bootstrap, así que se quedaba roja mientras el resto se
   ponía gris — lo que el usuario vio "extraño" el 2026-09-25. Ahora el realce de
   una fila crítica es un rojo más hondo, y la primera celda apila las dos sombras. */
.table-hover > tbody > tr.row-critical:hover > * {
  --bs-table-bg-state: color-mix(in srgb, var(--sev-critical-border) 40%, transparent);
  color: var(--sev-critical-text);
}
tr.row-critical > td:first-child {
  box-shadow:
    inset 3px 0 0 var(--sev-critical-border),
    inset 0 0 0 9999px var(--bs-table-bg-state, transparent);
}

/* La variante de **texto suelto**, para la fila del panel: ahí la fecha va sin
   pastilla porque la fila ya lleva dos (`LV-148`), así que se unifica la paleta y
   no la forma. El peso es lo que separa `warning` de `caution`. */
.sev-text-critical { color: var(--sev-critical-text); font-weight: 700; }
.sev-text-warning  { color: var(--sev-warning-text);  font-weight: 700; }
.sev-text-caution  { color: var(--sev-caution-text);  font-weight: 600; }
.sev-text-advisory { color: var(--sev-advisory-text); }
.sev-text-nominal  { color: inherit; }

/* UX-03: cifras que se comparan tienen que alinearse.
   `11/14` sobre `8/16` en dos tarjetas contiguas se leen mal si cada dígito mide
   distinto. Se aplica donde hay números que se contrastan entre filas o
   tarjetas, no globalmente: en prosa, `tabular-nums` empeora el ritmo. */
.table td,
.table th,
.readiness-value,
.kpi-value,
time,
.sev-text-critical,
.sev-text-warning,
.sev-text-caution,
.sev-text-advisory {
  font-variant-numeric: tabular-nums;
}

/* ── LV-D2: cabeceras y botones legibles en oscuro ─────────────────────────
   El th en gris tenue se leía "apagado" y el borde del botón outline
   (--ac-border) era casi invisible sobre el fondo oscuro, así que los botones
   no se percibían como botones (revisión en vivo 2026-08-03). */
[data-theme="dark"] .table th {
  color: #cdd8e6;
}
[data-theme="dark"] .btn-outline-secondary,
[data-theme="dark"] .table .btn-outline-secondary {
  color: var(--ac-text);
  border-color: var(--ac-text-muted);
}
[data-theme="dark"] .btn-outline-secondary:hover,
[data-theme="dark"] .table .btn-outline-secondary:hover {
  background-color: var(--ac-surface-raised);
  border-color: var(--ac-text-secondary);
  color: var(--ac-text);
}

/* ── LV-D7: tablas más parejas y alineadas ─────────────────────────────────
   Filas centradas verticalmente y celdas que no parten fechas ni pastillas, así
   las columnas quedan igualadas y legibles. */
.table td,
.table th {
  vertical-align: middle;
}
.table td .badge {
  white-space: nowrap;
}
/* The first column is the row's anchor (a code or registration): keep it on one
   line so rows line up instead of some wrapping to two lines.
   LV-57 scopes this out of .table-normalized, whose first column is a
   deliberate two-line identity block -- there the nowrap applies to the
   anchor line only (see the LV-57 section at the end of this file). */
.table:not(.table-normalized) td:first-child,
.table:not(.table-normalized) th:first-child {
  white-space: nowrap;
}
.table > :not(caption) > * > * {
  padding-block: 0.55rem;
}

/* ── LV-D5: panel más jerárquico ───────────────────────────────────────────
   El número del KPI protagoniza (label como eyebrow arriba), los títulos de
   sección son uniformes, y la franja de cumplimiento lleva un acento lateral
   para leerse como aviso accionable, no como un bloque más. */
.summary-card small:first-of-type {
  text-transform: uppercase;
  letter-spacing: 0.03em;
  font-size: 0.72rem;
}
.summary-card strong {
  font-size: 2rem;
}
.section-title {
  text-transform: uppercase;
  letter-spacing: 0.04em;
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--ac-text-secondary);
  margin-bottom: 0.5rem;
}
[data-theme="dark"] .section-title {
  color: #cdd8e6;
}
.compliance-strip {
  border-left: 3px solid var(--ac-turquoise);
}
/* LV-D9: KPIs in an even grid so N tiles share the row equally (no lone tile
   wrapping underneath), and each tile lays out consistently. */
.kpi-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(175px, 1fr));
  gap: 0.6rem;
}
.kpi-grid .summary-card {
  height: 100%;
}

/* ── LV-38 / LV-151: roster de casillas con buscador ───────────────────────
   El formulario del permiso lista ~40 operadores y ~16 aeronaves como
   CheckboxSelectMultiple; apilados de a uno por línea empujaban el resto del
   formulario fuera de la pantalla (revisión en vivo 2026-08-03), y LV-38 los
   pasó a columnas dentro de una caja acotada con scroll.

   LV-151 mueve estos estilos de `#div_id_operators fieldset > div` a la clase
   `.roster-options`. No es cosmética: la caja la dibuja ahora
   `operations/_roster_field.html`, que renderiza el campo a mano para poder
   ponerle la barra de búsqueda arriba -- crispy dibuja un checkbox múltiple con
   su propia plantilla del paquete y no deja gancho. De paso el selector deja de
   depender de la forma del DOM de una librería de terceros, que era lo frágil:
   una versión nueva de crispy que cambiara `fieldset > div` habría dejado el
   roster sin caja y sin que ningún test lo notara. */
/* LV-222: de multicolumna a rejilla, que es la causa del desalineado.

   Pedido del usuario con captura: *"sigue viendo desalineado al buscar operador y
   RPA; buscar una forma de que el orden sea prolijo en las listas"*. Al filtrar
   "fran" quedaban "Francisca Fredes Araya" y "Franco Cereceda Espinoza" en
   columnas de **alturas distintas**, con el nombre largo partido en dos líneas y
   la casilla del vecino a media altura. Con la aeronave pasaba igual, porque su
   etiqueta lleva matrícula, modelo y número de serie (`R5.5`) y ocupa tres.

   **La culpa era de `column-width`.** El layout multicolumna hace fluir cada
   elemento por su cuenta: nada relaciona la altura de un elemento con la del que
   está a su lado, así que una etiqueta de dos líneas corre todo lo que sigue en
   su columna y desalinea respecto a la de al lado. `break-inside: avoid` evitaba
   que un elemento se partiera en dos columnas, que es otro problema.

   En una rejilla, en cambio, **las celdas de una misma fila comparten altura por
   definición**, así que las casillas quedan alineadas sin importar cuántas líneas
   ocupe cada rótulo. `align-items: start` es lo que las pega arriba de su fila:
   sin eso, `.form-check` se centraría verticalmente y una casilla volvería a
   quedar a media altura de su vecina alta — el síntoma exacto de la captura.

   **No se cambió al desplegable con buscador que el usuario propuso**, y no por
   descarte: estos dos campos son de selección múltiple —un permiso lleva varios
   operadores y varias aeronaves (`OPS-4`)— y `LV-151` eligió la rejilla de
   casillas justamente para que lo ya elegido se vea de un vistazo, tras el pedido
   literal *"tengo problemas a buscar los operadores"*. Un desplegable múltiple
   esconde lo elegido detrás de un clic, que es lo que esa fila vino a arreglar.
   La rejilla con filas parejas da el orden pedido sin perder lo ganado.

   `auto-fill` con `minmax` conserva la medida de `LV-38`: 210 px es el ancho
   mínimo con el que un nombre completo entra sin partirse, y las columnas que
   caben las decide el ancho disponible — que ahora además cambia con la barra de
   navegación (`LV-208`). */
.roster-options {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 0.1rem 1.25rem;
  align-items: start;
  max-height: 15rem;
  overflow-y: auto;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--ac-border);
  border-radius: 0.5rem;
  background: var(--ac-surface-raised);
}
.roster-options.is-invalid { border-color: var(--bs-danger); }
/* El estado vacío (`{% empty %}`) es un `<p>` suelto dentro de la rejilla: que
   ocupe la fila entera en vez de una celda de 210 px. */
.roster-options > p { grid-column: 1 / -1; }

/* LV-222, segunda mitad: **el desorden que quedaba no lo veía la medición.**
   Con la rejilla puesta, los `top` de todas las casillas de una fila ya
   coincidían al milímetro — y en pantalla la lista seguía torcida. `.form-check`
   de Bootstrap coloca el input con `float: left` y compensa con `padding-left`
   en el contenedor, así que **una etiqueta que no entra en una línea baja por
   debajo del float** y arranca pegada al margen izquierdo, con la casilla
   flotando sola arriba. Con "Alvaro Andres Arias Malermo" en una celda de 235 px,
   o con la etiqueta de una aeronave —matrícula, modelo y número de serie
   (`R5.5`)— eso es la mayoría de las filas.

   Se reemplaza el float por flex: la casilla y el texto quedan lado a lado y el
   texto que envuelve lo hace **indentado bajo sí mismo**, no bajo la casilla.
   `align-items: flex-start` lo pega a la primera línea del rótulo, y `min-width:
   0` es lo que permite al texto encogerse dentro de la celda en vez de forzarla
   a crecer.

   `margin-top` en la casilla y no `align-items: center`: centrarla contra un
   rótulo de tres líneas la dejaría a media altura, que es justo el síntoma que el
   usuario fotografió. Los 0.2 rem la alinean con la primera línea del texto. */
.roster-option {
  display: flex;
  align-items: flex-start;
  gap: 0.45rem;
  padding-left: 0;
  margin-bottom: 0;
}
.roster-option .form-check-input {
  float: none;
  flex: 0 0 auto;
  margin: 0.2rem 0 0 0;
}
.roster-option .form-check-label {
  min-width: 0;
  cursor: pointer;
}
.roster-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.35rem;
}
/* El contador va junto al rótulo y no bajo la caja: con la caja en scroll, lo
   que está debajo puede quedar fuera de la vista justo cuando hace falta. */
.roster-count {
  color: var(--ac-text-secondary);
  font-size: 0.78rem;
  font-variant-numeric: tabular-nums;
}
.roster-tools {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 0.5rem;
}
.roster-search { max-width: 22rem; }
.roster-empty { margin-top: 0.4rem; }

/* ── LV-217: el tercer color de tipo, el que Bootstrap no da ───────────────
   La píldora del tipo usa cuatro tonos (ver `digest.SUBJECT_TONE_CSS`), y tres
   salen de pares `-subtle`/`-emphasis` de Bootstrap. Éste no puede: de las ocho
   familias, tres están reservadas a la escala de urgencia (`danger`, `warning`,
   `info`) y de las cinco restantes sólo dos son cromáticas — las otras tres son
   grises que, medidos en el navegador, quedan a tres puntos de canal unos de
   otros. `bg-dark-subtle` daba `rgb(233,235,238)` contra el `rgb(236,239,244)` de
   `secondary`: el mismo gris a la vista, y dos de los cuatro tipos sin distinguir.

   Púrpura y no el turquesa de marca: el turquesa está demasiado cerca del verde
   de `success`, que ya se usa para los vencimientos de personas.

   Los cuatro valores están medidos, no elegidos a ojo — ver el registro de la
   fila. El mínimo que tienen que cumplir es 4.5:1 de texto sobre su propio fondo
   (AA para texto pequeño), en los dos temas. */
.badge-kind-aircraft {
  background-color: #ece5f8;
  color: #4a2c8f;
}

/* El tema oscuro se declara por `[data-theme]` y no sólo por
   `prefers-color-scheme`: la app tiene su propio interruptor, y `theme-init.js`
   escribe el atributo en `<html>`. */
[data-theme="dark"] .badge-kind-aircraft {
  background-color: #2a1f45;
  color: #cbb8f0;
}

/* ── LV-146: la faena como chip de identidad ───────────────────────────────
   Contorno sin relleno a propósito: en la fila del panel el chip convive con el
   badge de tipo (`bg-secondary-subtle`), y dos pastillas rellenas del mismo gris
   se leen como una sola cosa partida en dos. El contorno lo separa sin agregar
   un color nuevo al vocabulario.

   0.76rem es el piso documentado para metadatos en este archivo: por debajo de
   eso ningún contraste rescata el texto. Todo con tokens `--ac-*`, que se
   redefinen por tema, así que el chip sigue al tema claro y al oscuro sin una
   segunda regla.

   `--ac-text-secondary` sobre `--ac-surface`/`--ac-surface-raised` es el par que
   ya usan `.roster-count` y `.table-primary-sub`, o sea contraste ya medido en
   los dos temas; el guion de la variante vacía baja a `--ac-text-muted` porque
   es ausencia de dato, no dato. */
.cc-chip {
  display: inline-block;
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  padding: 0.14rem 0.45rem;
  border-radius: 999px;
  border: 1px solid var(--ac-border);
  background: transparent;
  color: var(--ac-text-secondary);
  white-space: nowrap;
}
.cc-chip.is-empty { color: var(--ac-text-muted); }

/* ── Calendar ─────────────────────────────────────── */
/* LV-47: .calendar-noscript (wraps .legacy-calendar-header + .calendar-fallback)
   is hidden by calendar.js once FullCalendar renders, not by CSS -- a static
   `display: none` here left the no-JS fallback with no visible month title or
   working nav in the one scenario it exists for (JS/CDN failure). */
.calendar-shell { overflow: visible; }
.calendar-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 1rem; flex-wrap: wrap; }
.calendar-legend { display: flex; gap: 0.85rem; flex-wrap: wrap; color: var(--ac-text-secondary); font-size: 0.78rem; }
.calendar-legend span { display: inline-flex; align-items: center; gap: 0.35rem; }
.calendar-dot { width: 0.6rem; height: 0.6rem; border-radius: 50%; display: inline-block; }
.calendar-dot-permission { background: #f59e0b; }
.calendar-dot-assignment { background: #2563eb; }
.calendar-dot-flight { background: #0ea5e9; }
.calendar-dot-maintenance { background: #8b5cf6; }
.calendar-dot-compliance { background: #ef4444; }
/* LV-78: el punto del carril "Acciones" se fue con la leyenda. */
.calendar-filter-label { display: flex; align-items: center; gap: 0.5rem; color: var(--ac-text-secondary); font-size: 0.78rem; font-weight: 700; }
.calendar-filter-label .form-select { min-width: 170px; }
.calendar-scope-filters { display: flex; flex: 1 1 100%; gap: 0.6rem; flex-wrap: wrap; }
.calendar-scope-filters .form-select { min-width: 190px; flex: 1 1 190px; }
#calendar-app { min-height: 520px; }
.fc { --fc-border-color: var(--ac-border); --fc-page-bg-color: var(--ac-surface); --fc-neutral-bg-color: var(--ac-surface-raised); --fc-list-event-hover-bg-color: var(--ac-primary-soft); color: var(--ac-text); }
.fc .fc-toolbar-title { font-size: 1.2rem; color: var(--ac-text); }
.fc .fc-button { background: var(--ac-surface-raised); border-color: var(--ac-border); color: var(--ac-text); min-height: 36px; }
.fc .fc-button:hover, .fc .fc-button-primary:not(:disabled).fc-button-active { background: var(--ac-primary); border-color: var(--ac-primary); color: #fff; }
.fc .fc-daygrid-day-number { color: var(--ac-text-secondary); }
.fc .fc-col-header-cell-cushion { color: var(--ac-text-secondary); font-size: 0.78rem; text-transform: uppercase; }
/* Month-view events wrap to two lines instead of clipping mid-word
   ("Maria Gonzale…"), and the cell grows to fit them. */
.fc-event { border: 0; border-radius: 5px; padding: 0.15rem 0.35rem; font-size: 0.78rem; font-weight: 700; cursor: pointer; }
.fc-daygrid-event .fc-event-title { white-space: normal; overflow-wrap: anywhere; line-height: 1.25; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.fc .fc-daygrid-day-frame { min-height: 6.2rem; }
.fc-daygrid-event { margin-block: 1px; }

.calendar-fallback .calendar-day { min-width: 8rem; height: auto; min-height: 105px; padding: 0.45rem; text-align: left; vertical-align: top; }
.calendar-day.is-empty { background: var(--ac-bg); }
.calendar-day.is-today { background: var(--ac-primary-soft); }
.calendar-event { display: block; overflow: hidden; margin-top: 0.35rem; padding: 0.2rem 0.35rem; border-left: 3px solid currentColor; border-radius: 4px; color: var(--ac-text-secondary); font-size: 0.72rem; text-decoration: none; line-height: 1.25; overflow-wrap: anywhere; }
.calendar-event:hover { color: var(--ac-text); background: var(--ac-primary-soft); }
.event-permission { color: #b26900; }
.event-maintenance { color: #7650c8; }
.event-task { color: #087f78; }
/* Those three are tuned for the light surface; on the dark surface they drop
   well under AA, so each gets a lighter counterpart. */
[data-theme="dark"] .event-permission, [data-bs-theme="dark"] .event-permission { color: #f0b45f; }
[data-theme="dark"] .event-maintenance, [data-bs-theme="dark"] .event-maintenance { color: #b79cf5; }
[data-theme="dark"] .event-task, [data-bs-theme="dark"] .event-task { color: #46c9bd; }

/* ── Administration center ───────────────────────── */
/* One column read top to bottom. The old 2x2 grid tied each card's height to
   whether its title wrapped, so the columns never lined up. */
.admin-list { overflow: hidden; }
.admin-section + .admin-section { border-top: 1px solid var(--ac-border); }
.admin-section-header { padding: 1.15rem 1.25rem 0.75rem; }
.admin-section-title { margin: 0; font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ac-text-secondary); }
.admin-section-description { margin: 0.2rem 0 0; font-size: 0.85rem; color: var(--ac-text-muted); }
.admin-item { display: grid; grid-template-columns: 2.25rem 1fr auto; align-items: center; gap: 0.9rem; padding: 0.75rem 1.25rem; color: var(--ac-text); text-decoration: none; }
.admin-item + .admin-item { border-top: 1px solid var(--ac-border); }
.admin-item:hover, .admin-item:focus-visible { background: var(--ac-primary-soft); color: var(--ac-text); }
/* A distinct glyph per row. They were all the same cog, so the column carried
   no information and only repeated itself nine times. */
.admin-item-icon { width: 2.25rem; height: 2.25rem; padding: 0.45rem; border-radius: 9px; background: var(--ac-primary-soft); color: var(--ac-primary-hover); fill: none; stroke: currentColor; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }
[data-theme="dark"] .admin-item-icon, [data-bs-theme="dark"] .admin-item-icon { color: var(--ac-primary); }
.admin-item-text strong { font-weight: 700; }
.admin-item small { display: block; margin-top: 0.1rem; color: var(--ac-text-secondary); font-size: 0.85rem; }
.admin-item-badge { margin-left: 0.35rem; padding: 0.05rem 0.4rem; border: 1px solid var(--ac-border); border-radius: 999px; font-size: 0.72rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: var(--ac-text-secondary); }
.admin-item-arrow { color: var(--ac-primary-hover); font-size: 1.1rem; }
[data-theme="dark"] .admin-item-arrow, [data-bs-theme="dark"] .admin-item-arrow { color: var(--ac-primary); }

@media (max-width: 600px) { .admin-item { grid-template-columns: 2.25rem 1fr; } .admin-item-arrow { display: none; } }

/* ── Workboard ────────────────────────────────────── */
/* Action cells keep one height whatever the row's state: a resolved alert shows
   a badge and one button, an open one shows two, and they must line up. */
.alert-actions {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  justify-content: flex-end;
  min-height: 34px;
}

/* LV-110: the buttons in this column are all the same width, not the width of
   their own word. "Resolver", "Deshacer" and "Verificar" differ in length, so
   without this the column looks ragged row to row -- and they are actions of
   the same rank. */
.alert-actions .btn {
  min-width: 6.5rem;
}

/* LV-110: the resolution reason moved to its own column, so it reads
   left-to-right like any other text instead of being pinned to the right edge
   under the buttons. Still capped and wrapped: a long root cause must not
   stretch the column past the rest of the table. */
.alert-reason {
  max-width: 28rem;
  overflow-wrap: anywhere;
}

/* LV-72: the SIGO-style progress stepper on the flight permit. Badges joined
   by a line, filled up to the step reached. The connector is drawn on each
   step's ::before and pointed at the previous one, so the number of steps
   comes entirely from the template loop -- a status added to the model's
   STATUS_FLOW needs no CSS change. */
/* R8.3: los iconos del pronóstico. Trazo, no relleno, igual que los del menú
   lateral, y `currentColor` para que sigan al texto en claro y en oscuro sin
   una segunda regla. Van dentro del <small>, así que se alinean con la
   etiqueta y no compiten con el número. */
.weather-icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.8;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.weather-grid .summary-card small {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

/* R8.4: la cabecera del pronóstico. La temperatura y el dibujo del cielo son
   lo que se lee de una mirada, así que van arriba y en grande; los cuatro
   números de decisión quedan debajo, del mismo tamaño entre sí para que
   ninguno parezca más importante que otro. El icono usa el mismo trazo que el
   resto de la app y `currentColor`, así que sigue al tema claro/oscuro sin una
   segunda regla. */
/* LV-89: la tira de "¿puedo operar hoy?". Misma grilla pareja que las tarjetas
   de arriba (LV-D9) para que se lean como una sola familia y no como dos
   bloques que compiten. */
.readiness-strip {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.75rem;
}

.readiness-pct {
  font-size: 0.8em;
  font-weight: 600;
  color: var(--ac-text-secondary);
  margin-left: 0.35rem;
}

/* Bajo la meta acordada: el único rojo de la tira, y sólo donde hay una meta
   contra la cual estar bajo. */
.readiness-pct.is-below {
  color: var(--ac-danger);
}

/* LV-89: la ventana de luz, bajo la temperatura y sobre las cifras. */
.weather-daylight {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: -0.25rem 0 0.9rem;
  font-size: 0.92rem;
  color: var(--ac-text-secondary);
}

.weather-uv::before {
  content: "·";
  padding: 0 0.4rem;
}

/* LV-88: el trayecto en una sola celda. La flecha es el separador, no un dato:
   se atenúa para que los dos códigos de centro de costo sean lo que se lee. */
.movement-arrow {
  color: var(--ac-text-muted);
  padding: 0 0.15rem;
}

/* LV-87: el respaldo de la credencial, como figura. Verde cuando está y gris
   —no rojo— cuando falta: un PDF que no se subió es una tarea pendiente, no un
   incumplimiento; el rojo de esta app está reservado para lo vencido. */
.credential-file svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Bootstrap's own success emphasis, which already flips with data-bs-theme --
   this app has no --ac-success token of its own to reuse. */
.credential-file.is-present {
  color: var(--bs-success-text-emphasis, #2e7d55);
}

.credential-file.is-missing {
  color: var(--ac-text-muted);
}

/* LV-85: el visor del documento. Alto fijo y no "todo el alto del PDF": la
   ficha tiene contenido debajo (versiones anteriores) y un visor sin techo lo
   empuja fuera de la pantalla. El navegador pone su propia barra de
   herramientas dentro del iframe, así que no hace falta ninguna nuestra. */
.document-preview {
  width: 100%;
  height: 70vh;
  min-height: 400px;
  border: 1px solid var(--ac-border);
  border-radius: 6px;
  background: var(--ac-surface-raised);
}

.document-preview-image {
  max-width: 100%;
  height: auto;
  border: 1px solid var(--ac-border);
  border-radius: 6px;
}

.weather-hero {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}

.weather-hero-icon {
  width: 52px;
  height: 52px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  color: var(--ac-primary);
}

.weather-temp {
  display: block;
  font-size: 2.25rem;
  line-height: 1.1;
  font-weight: 600;
}

.weather-temp-min {
  font-size: 0.6em;
  font-weight: 600;
  color: var(--ac-text-muted);
}

.weather-condition {
  display: block;
  font-size: 0.92rem;
  color: var(--ac-text-secondary);
}

.status-steps {
  display: flex;
  list-style: none;
  margin: 0;
  padding: 0;
}

.status-step {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  padding: 0 0.5rem;
  text-align: center;
  min-width: 0;
}

.status-step:not(:first-child)::before {
  content: "";
  position: absolute;
  top: 15px;
  left: -50%;
  right: 50%;
  height: 2px;
  background: var(--ac-border);
}

.status-step.is-done:not(:first-child)::before,
.status-step.is-current:not(:first-child)::before,
.status-step.is-blocked:not(:first-child)::before {
  background: var(--ac-primary);
}

.status-step-badge {
  position: relative;
  z-index: 1;
  display: inline-grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 2px solid var(--ac-border);
  border-radius: 50%;
  background: var(--ac-surface);
  color: var(--ac-text-secondary);
  font-size: 0.85rem;
  font-weight: 700;
}

.status-step-label {
  font-size: 0.85rem;
  overflow-wrap: anywhere;
}

.status-step.is-done .status-step-badge {
  background: var(--ac-primary);
  border-color: var(--ac-primary);
  color: #fff;
}

.status-step.is-current .status-step-badge {
  border-color: var(--ac-primary);
  color: var(--ac-primary-hover);
  box-shadow: 0 0 0 4px var(--ac-primary-soft);
}

.status-step.is-current .status-step-label { font-weight: 700; }

/* A denied permit is not "pending the rest": the flow stopped here. */
.status-step.is-blocked .status-step-badge {
  background: var(--ac-danger);
  border-color: var(--ac-danger);
  color: #fff;
}

.status-step.is-pending .status-step-label { color: var(--ac-text-secondary); }

@media (max-width: 576px) {
  /* Stacked, with the connector running down the left edge instead of across:
     four badges side by side stop being legible well before this width. */
  .status-steps { flex-direction: column; gap: 0.85rem; }
  .status-step { flex-direction: row; align-items: flex-start; padding: 0; text-align: left; }
  .status-step:not(:first-child)::before {
    top: -0.85rem;
    left: 15px;
    right: auto;
    width: 2px;
    height: 0.85rem;
  }
}

.view-tabs { display: flex; gap: 0.35rem; border-bottom: 1px solid var(--ac-border); }
.view-tab { padding: 0.65rem 0.85rem; border-bottom: 3px solid transparent; color: var(--ac-text-secondary); font-weight: 700; text-decoration: none; }
.view-tab:hover { color: var(--ac-text); background: var(--ac-primary-soft); }
/* Underline keeps --ac-primary (a border has no contrast floor to meet); the
   label takes the darker step for the same reason as .eyebrow. */
.view-tab.is-active { border-bottom-color: var(--ac-primary); color: var(--ac-primary-hover); }
[data-theme="dark"] .view-tab.is-active,
[data-bs-theme="dark"] .view-tab.is-active { color: var(--ac-primary); }
.workboard-toolbar { display: flex; justify-content: space-between; gap: 1rem; align-items: center; flex-wrap: wrap; padding: 0.8rem; background: var(--ac-surface); border: 1px solid var(--ac-border); border-radius: 10px; }
.workboard-toolbar .kanban-filter { min-width: 145px; }
.table-group-row th { background: var(--ac-surface-raised); font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--ac-text-secondary); }
.kanban-board { min-height: 58vh; padding: 0.15rem 0.15rem 1rem; gap: 0.85rem; }
.kanban-column { background: var(--ac-surface-raised); border: 1px solid var(--ac-border); border-radius: 12px; min-width: 285px; max-width: 330px; box-shadow: none; }
.kanban-column-header { background: var(--ac-surface); border-top-width: 4px; color: var(--ac-text); padding: 0.85rem 0.9rem; }
.kanban-count { background: var(--ac-primary-soft); color: var(--ac-text-secondary); }
.kanban-cards { min-height: 120px; padding: 0.65rem; }
.kanban-card { background: var(--ac-surface); border: 1px solid var(--ac-border); border-left-width: 4px; box-shadow: 0 4px 12px rgba(18,34,59,.06); }
.kanban-card:hover { border-color: var(--ac-primary); box-shadow: 0 8px 18px rgba(18,34,59,.11); }
/* Titles are the densest text on the board; a permanent underline made every
   card look heavy, so it moves to hover/focus while staying a real link. */
.kanban-card-title { display: block; color: var(--ac-text); font-size: 0.92rem; font-weight: 600; line-height: 1.35; text-decoration: none; }
.kanban-card-title:hover, .kanban-card-title:focus-visible { color: var(--ac-primary); text-decoration: underline; }
.kanban-assignee, .kanban-due { color: var(--ac-text-secondary); }
.kanban-footer { border-top-color: var(--ac-border); }
.kanban-footer .btn { border-style: dashed; }
.kanban-source { margin-top: 0.55rem; color: var(--ac-text-secondary); font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.kanban-source span { margin-right: 0.25rem; color: var(--ac-text-muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.task-source { padding: 0.7rem 0.8rem; border: 1px solid var(--ac-border); border-radius: 8px; background: var(--ac-surface-raised); }
.kanban-empty { color: var(--ac-text-muted); }
.kanban-progress .progress { height: 5px; margin-top: 0.35rem; background: var(--ac-border); }
.kanban-progress .progress-bar { background: var(--ac-primary); }

@media (max-width: 768px) {
  .view-tabs { overflow-x: auto; white-space: nowrap; -webkit-overflow-scrolling: touch; }
  .kanban-board { -webkit-overflow-scrolling: touch; overscroll-behavior-inline: contain; scroll-snap-type: inline proximity; }
  .kanban-column { scroll-snap-align: start; }
  .workboard-toolbar { align-items: stretch; }
  .workboard-toolbar > * { width: 100%; }
  .workboard-toolbar .kanban-filter { width: 100%; }
}

/* ── BLOQUE GEO — read-only map island (GEO-7) ──────────────────────────── */
/* LV-136: el mapa medía 480 px fijos, así que en una pantalla de 1080 sobraba
   la mitad del alto y editar obligaba a arrastrar el lienzo en vez de mirarlo.
   Ahora crece con la ventana -- `clamp` sube el techo sin colapsarlo en una
   pantalla baja, donde 480 px sigue siendo el piso -- y la altura la fija el
   contenedor para que el panel de capas mida siempre lo mismo que el mapa (antes
   eran dos números que había que acordarse de mover juntos). */
.geo-map-wrap {
  display: flex;
  gap: 0;
  height: clamp(480px, 68vh, 900px);
}
.geo-map {
  flex: 1 1 auto;
  height: 100%;
  min-height: 0;
  z-index: 0; /* keep Leaflet panes under the app sidebar/modals */
}
.geo-panel {
  flex: 0 0 240px;
  max-width: 240px;
  overflow-y: auto;
  max-height: 100%;
  border-left: 1px solid var(--ac-border);
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  font-size: 0.85rem;
}
.geo-panel-head {
  padding: 0.5rem 0.75rem;
  font-weight: 600;
  color: var(--ac-text-secondary);
  border-bottom: 1px solid var(--ac-border);
}
.geo-panel-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.35rem 0.75rem;
  margin: 0;
  cursor: pointer;
}
.geo-panel-label {
  flex: 1 1 auto;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.geo-panel-count {
  flex: 0 0 auto;
  color: var(--ac-text-secondary);
  font-variant-numeric: tabular-nums;
}
/* LV-136: "Ampliar" -- la tarjeta del mapa ocupa la ventana entera para dibujar.
   Es un estado de la tarjeta y no una ventana nueva, así que los controles de
   edición, el panel de capas y los botones de Deshacer/Rehacer/Guardar siguen
   siendo los mismos elementos: no hay una segunda copia del editor que después
   se desincronice.

   `z-index` 1045: sobre la barra lateral (1040) y bajo el modal de Bootstrap
   (1055), para que un diálogo abierto desde el mapa siga quedando encima. */
.geo-map-card.geo-expanded {
  position: fixed;
  inset: 0;
  z-index: 1045;
  margin: 0;
  border-radius: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}
.geo-map-card.geo-expanded .geo-map-wrap {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}
/* El body no debe hacer scroll detrás de la tarjeta ampliada: arrastrar el mapa
   movería la página en vez del lienzo. */
body.geo-expanded-open {
  overflow: hidden;
}
/* BLOQUE 5 situation metrics */
.admin-metrics { display: flex; flex-wrap: wrap; gap: 0.75rem; }
.admin-metric {
  display: flex;
  flex-direction: column;
  min-width: 8rem;
  padding: 0.6rem 0.9rem;
  border: 1px solid var(--ac-border);
  border-radius: 8px;
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  text-decoration: none;
}
a.admin-metric:hover { border-color: var(--ac-text-secondary); }
.admin-metric-value { font-size: 1.5rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.admin-metric-label { font-size: 0.78rem; color: var(--ac-text-secondary); }
.admin-metric-warn { border-left: 3px solid #e08a00; }
.admin-metric-ok { border-left: 3px solid #22a06b; }
.admin-metric-muted { border-left: 3px solid var(--ac-border); }

/* GEO-11 editable layer tree */
.geo-tree { padding: 0.25rem 0; }
.geo-tree-root {
  padding: 0.3rem 0.75rem;
  font-family: var(--bs-font-monospace, monospace);
  color: var(--ac-text-secondary);
  cursor: pointer;
  border-bottom: 1px dashed var(--ac-border);
}
.geo-tree-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.25rem 0.5rem;
  cursor: grab;
}
.geo-tree-row.is-dragging { opacity: 0.5; }
.geo-tree-row.is-drop,
.geo-tree-root.is-drop { background: var(--ac-turquoise, #0f9f95); color: #fff; }
.geo-tree-row.is-active,
.geo-tree-root.is-active { background: color-mix(in srgb, var(--ac-turquoise, #0f9f95) 18%, transparent); }
.geo-tree-label {
  flex: 1 1 auto;
  min-width: 0; /* let the label shrink so actions align in one right column */
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.geo-tree-actions { flex: 0 0 auto; display: flex; gap: 0.25rem; margin-left: auto; }
.geo-tree-btn {
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.5rem;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ac-border);
  background: var(--ac-surface);
  color: var(--ac-text-secondary);
  border-radius: 4px;
  line-height: 1;
  padding: 0;
  font-size: 0.85rem;
  cursor: pointer;
}
.geo-tree-btn:hover { color: var(--ac-text); border-color: var(--ac-text-secondary); }
/* GEO-12a version diff */
.geo-diff-bar { display: inline-flex; align-items: center; gap: 0.35rem; }
.geo-diff-select { width: auto; min-width: 4.5rem; }
.geo-diff-dot {
  flex: 0 0 auto;
  width: 0.8rem;
  height: 0.8rem;
  border-radius: 50%;
  display: inline-block;
}
.geo-popup strong { display: block; }
.geo-popup-desc { margin: 0.25rem 0 0; white-space: pre-wrap; }
.geo-popup-measure { margin: 0.25rem 0 0; font-variant-numeric: tabular-nums; }

/* UX-28 (WCAG 2.2 §2.5.7): la tabla de coordenadas dentro del globo del
   elemento. Es la alternativa sin arrastre a mover vértices, y de paso lo que se
   transcribe a SIGO.

   El alto está acotado y desplaza: un círculo llega poligonalizado en 64
   vértices (los KMZ de Trimble de CC 738), y sin tope el globo taparía el mapa
   entero. */
/* == UX-29 · el chequeo prevuelo ===========================================
 *
 * Un acta, no un formulario. Firmado, la pantalla pierde los controles en vez de
 * atenuarlos: un formulario deshabilitado invita a buscar cómo habilitarlo. */
.preflight-signed { border-left: 5px solid var(--ac-primary); }
/* El «no conforme» registrado se ve **también después de firmar**, y de eso se
   trata: la firma no lo tapa, lo deja escrito. Ámbar y no rojo porque no es un
   error del sistema — es un hecho que alguien declaró y decidió asumir. */
.preflight-flagged {
  border-left: 5px solid var(--sev-warning-text);
  background: var(--sev-warning-bg);
  color: var(--sev-warning-text);
}
.preflight-items { margin: 0; padding-left: var(--sp-6); }
.preflight-item { padding: var(--sp-3) 0; border-bottom: 1px solid var(--ac-border); }
.preflight-item:last-child { border-bottom: 0; }
.preflight-item-text { font-weight: 600; }
.preflight-required { color: var(--sev-critical-text); }
/* El área de toque de cada opción llega al rótulo entero: se contesta en faena,
   en un teléfono, con guantes, y un radio de 13 px es imposible de acertar. */
.preflight-choice {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  padding: var(--sp-2) 0;
  cursor: pointer;
}
/* «Sin contestar» se dice, pero no se ofrece como opción: no es una respuesta,
   es el estado inicial. Ámbar para que un punto sin tocar se vea al bajar por la
   lista — que es cuando alguien va a ir a firmar. */
.preflight-pending { color: var(--sev-warning-text); font-size: var(--fs-xs); font-weight: 600; }

/* == UX-26 · el aviso de datos guardados ===================================
 *
 * Ámbar y arriba de todo. Ámbar y no rojo porque no es un error —la aplicación
 * está haciendo exactamente lo que se le pidió— pero tampoco es neutro: lo que
 * se está mirando puede tener un día de atraso, y en una pantalla de
 * vencimientos un día importa.
 *
 * `position: sticky` para que no se vaya al desplazar: quien está sin señal
 * puede estar leyendo una ficha larga, y el aviso tiene que seguir ahí cuando
 * llegue a la fecha que va a copiar. */
.offline-banner {
  position: sticky;
  top: 0;
  z-index: 1090; /* sobre la paleta: es una condición de toda la pantalla */
  padding: var(--sp-3) var(--sp-5);
  background: var(--sev-warning-bg);
  color: var(--sev-warning-text);
  border-bottom: 1px solid var(--sev-warning-border);
  font-size: var(--fs-md);
  font-weight: 600;
  text-align: center;
}

/* == UX-31 · los atajos del rol ============================================
 *
 * Una tira corta bajo el panel, antes de los grupos plegables. Se distingue del
 * menú de siempre por sangría y peso, no por color: los ocho colores de sección
 * (`LV-207`) significan *de qué módulo es* cada entrada, y un atajo puede
 * apuntar a cualquiera de ellos — teñirlos de un noveno color habría inventado
 * una sección que no existe. */
.nav-shortcuts { margin-bottom: var(--sp-4); }
.nav-shortcuts-label { display: block; }
.nav-item.nav-shortcut {
  /* Sin icono: el destino ya tiene el suyo más abajo, y repetirlo haría creer
     que el menú se duplicó. La sangría ocupa su lugar para que la tira se lea
     como una lista y no como cuatro entradas sueltas. */
  padding-left: var(--sp-6);
  font-weight: 600;
}
/* Colapsada la barra no hay sitio para texto sin icono, y una tira de rótulos
   cortados no dice nada: los atajos se retiran y el menú de siempre queda. */
.sidebar.is-collapsed .nav-shortcuts { display: none; }

/* == UX-27 · «¿Puedo volar?» ===============================================
 *
 * El veredicto tiene que resolverse de un vistazo: quien mira esto lo hace en un
 * teléfono, al sol, con guantes. Por eso el «Sí» o el «No» son tipografía grande
 * y color de fondo, y no una insignia más en una tabla.
 *
 * Los colores salen de los tokens que ya existen y no de unos propios: el rojo
 * de `--sev-critical-text` significa acá lo mismo que en la bandeja y en el
 * panel, que es la única forma de que tres pantallas no enseñen tres escalas.
 *
 * ⚠️ **Para el «sí» va `--ac-primary` y no `--sev-nominal-text`.** «Nominal» en
 * la escala de `UX-01` es gris a propósito —significa *"nada que reportar"*, y
 * pintarlo de color le robaría atención a lo que sí la necesita— y acá hace
 * falta lo contrario: el veredicto **es** la noticia de la pantalla. El
 * corporativo es el color afirmativo del producto y no toca la escala. */
.can-fly-yes { border-left: 5px solid var(--ac-primary); }
.can-fly-no { border-left: 5px solid var(--sev-critical-text); }
.can-fly-answer { font-size: var(--fs-xl); font-weight: 700; line-height: 1.1; }
.can-fly-yes .can-fly-answer { color: var(--ac-primary); }
.can-fly-no .can-fly-answer { color: var(--sev-critical-text); }
/* La aclaración de que la pantalla no autoriza: se atenúa, no se esconde. Es la
 * frase que evita que un «Sí» se lea como un permiso, así que tiene que estar a
 * la vista aunque no compita con el veredicto. */
.can-fly-caveat { font-size: var(--fs-md); color: var(--ac-text-muted); }
.can-fly-item { padding: var(--sp-2) 0; border-bottom: 1px solid var(--ac-border); }
.can-fly-item:last-child { border-bottom: 0; }
.can-fly-item-blocker { border-left: 3px solid var(--sev-critical-text); padding-left: var(--sp-3); }
.can-fly-item-warning { border-left: 3px solid var(--sev-warning-text); padding-left: var(--sp-3); }
.can-fly-check { padding: var(--sp-2) 0; }
/* ⚠️ La marca lleva `aria-hidden` y su significado va en un texto sólo para
 * lectores: un ✓ verde y una ✕ roja distinguidos **sólo por color y forma** no
 * llegan a quien usa lector de pantalla, y esta lista es justamente la que
 * responde "qué se dio por comprobado". */
.can-fly-check-mark { display: inline-block; width: 1.2rem; font-weight: 700; }
.can-fly-check-mark.is-ok { color: var(--ac-primary); }
.can-fly-check-mark.is-bad { color: var(--sev-critical-text); }

.geo-popup-coords { margin-top: var(--sp-3); border-top: 1px solid var(--ac-border); padding-top: var(--sp-2); }
.geo-popup-coords-title { font-weight: 600; font-size: var(--fs-md); }
.geo-popup-coords-hint { margin: var(--sp-1) 0 var(--sp-2); font-size: var(--fs-xs); color: var(--ac-text-muted); }
.geo-coord-table { display: block; max-height: 11rem; overflow-y: auto; font-size: var(--fs-xs); }
.geo-coord-table th[scope="row"] { padding-right: var(--sp-2); color: var(--ac-text-muted); font-weight: 400; }
.geo-coord-table td { padding: var(--sp-1) var(--sp-1) var(--sp-1) 0; }
/* Anchura fija y cifras tabulares: una columna de coordenadas que baila con cada
   dígito es ilegible justo cuando hay que compararlas entre sí. */
.geo-coord-input { width: 8.5rem; font-variant-numeric: tabular-nums; }
@media (max-width: 768px) {
  /* En columna la altura la ponen los hijos: con la del contenedor fija, el
     mapa y el panel se pelearían los mismos píxeles y el mapa perdería. */
  .geo-map-wrap { flex-direction: column; height: auto; }
  .geo-map { height: 60vh; min-height: 320px; }
  .geo-panel { flex-basis: auto; max-width: none; max-height: 160px; border-left: 0; border-top: 1px solid var(--ac-border); }
}

/* Leaflet renders its controls and popups on their own light surfaces. The
   app's dark theme sets a light text color on containers, which the layer
   switcher inherits -> light text on white, unreadable. Force dark, theme-
   independent text on Leaflet's own chrome (the map tiles are unaffected). */
.leaflet-container .leaflet-control-layers,
.leaflet-container .leaflet-control-layers label,
.leaflet-container .leaflet-control-layers-list {
  color: #1b2737;
}

/* GEO-8 editor chrome */
.geo-banner {
  padding: 0.5rem 0.9rem;
  background: #7a2e2e;
  color: #fff;
  font-size: 0.85rem;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.25rem;
}
.geo-dialog {
  border: 1px solid var(--ac-border);
  border-radius: 0.5rem;
  background: var(--ac-surface-raised);
  color: var(--ac-text);
  padding: 1rem;
  width: min(420px, 92vw);
}
.geo-dialog::backdrop { background: rgba(0, 0, 0, 0.5); }
.geo-popup-edit input,
.geo-popup-edit textarea { margin-bottom: 0.35rem; }
/* Geoman's toolbar sits on its own light surface, like Leaflet's controls. */
.leaflet-container .leaflet-pm-toolbar .leaflet-pm-actions-container { color: #1b2737; }

/* ── LV-57: una sola anatomía de fila para las listas del padrón ───────────
   Centros de costo, Aeronaves, Operadores y Habilitaciones repartían el ancho
   cada una a su manera (la columna con el texto más largo se quedaba con el
   espacio), y sólo Aeronaves usaba el bloque de identidad de dos líneas. Este
   vocabulario lo generaliza: zonas fijas IDENTIDAD | ATRIBUTOS | ESTADO |
   ACCIÓN, anchos declarados en un <colgroup> por lista.

   Opt-in a propósito: .table-normalized sólo actúa donde hay <colgroup>, así
   que las demás listas (asignaciones, mantenimiento, permisos, documentos,
   alertas…) siguen con el reparto automático, byte por byte igual que antes. */
.table-normalized {
  table-layout: fixed;
  /* Con anchos en porcentaje, una pantalla estrecha aplastaría las columnas
     hasta hacerlas ilegibles; por debajo de esto scrollea (.table-responsive
     ya envuelve la tabla). */
  min-width: 900px;
}

/* Anchos por rol de columna. Van sobre <col>, no sobre las celdas: así el
   reparto se lee de un vistazo en la plantilla y no hay estilos inline.

   Las 4 listas fijan igual la primera columna (26%) y la de acción (110px):
   ese es el efecto buscado -- el ancla y el botón caen en la misma posición
   en las cuatro pantallas. Cada lista deja además UNA columna sin ancho
   (.col-flex o .col-chips) que absorbe el sobrante, de modo que los
   porcentajes no tienen que sumar exacto pese a que cada lista tiene un
   número distinto de columnas. */
/* `UX-11`/`UX-07`: la columna de casillas, que **inyecta `worktable.js`** junto
   con su `<col>`. Sin ella declarada acá, `table-layout: fixed` le repartía el
   26 % de `.col-primary` -- que es exactamente lo que se vio en producción: una
   primera columna enorme y vacía, con todo lo demás corrido un lugar. Sólo la
   casilla y su aire; no hay contenido que ajustar. */
.table-normalized .col-select { width: 3%; }
.table-normalized .col-primary { width: 26%; }
.table-normalized .col-text { width: 18%; }
.table-normalized .col-compact { width: 12%; }
.table-normalized .col-numeric { width: 8%; }
.table-normalized .col-status { width: 13%; }
/* LV-162: una columna cuyo dato es UN icono centrado (el sí/no del respaldo
   adjunto, LV-87). Estaba declarada .col-status, y 13% son 203 px para dibujar
   una cruz de 14 px: ese ancho salía del sobrante de la columna de texto libre
   de al lado, que es la que de verdad lo necesita. El encabezado sí es largo
   ("Credencial adjunta") y por eso envuelve -- ver la regla de thead abajo. */
.table-normalized .col-icon { width: 6%; }
.table-normalized .col-flex,
.table-normalized .col-chips { width: auto; }
.table-normalized .col-actions { width: 110px; }
/* LV-254: documentos tiene tres botones por fila (ver, reemplazar, eliminar), que
   en 110 px se apilaban en tres líneas. */
.table-normalized .col-actions-wide { width: 250px; }

/* LV-162: en una tabla de anchos fijos el encabezado envuelve en vez de
   desbordar.

   `.table th` fija `nowrap` a propósito y con dos razones escritas (un
   encabezado de dos líneas junto a vecinos de una, y Bootstrap alineándolos
   abajo mientras las celdas van al medio). La primera sigue valiendo; la
   segunda ya no, porque `.table th` declara `vertical-align: middle` desde
   entonces. Pero la premisa que cierra ese comentario -- "la tabla vive en
   .table-responsive, así que un encabezado ancho scrollea en vez de romper el
   layout" -- es cierta con reparto automático y **falsa acá**: con
   `table-layout: fixed` el encabezado no ensancha la tabla, así que no
   scrollea, se sale encima de la columna vecina. Es lo que el usuario reportó
   el 2026-08-27 con "CENTRO DE COSTO" pisando "HABILITACIONES".

   Y no alcanza con repartir bien los anchos: a 900 px (el `min-width` de esta
   tabla) un 12% son 108 px y ese rótulo mide 125, así que la colisión volvería
   en cuanto alguien angoste la ventana. Envolver convierte el peor caso en un
   encabezado de dos líneas, que se lee. */
.table-normalized thead th {
  white-space: normal;
  /* LV-177: `break-word` corta **a mitad de palabra** cuando una sola no cabe,
     y eso fue lo que el usuario reportó el 2026-08-28: "CREDENCIA / L ADJUNTA"
     en la columna del 6%. Se conserva porque es el último recurso correcto —sin
     él la palabra desborda y pisa a la vecina, que es peor— pero deja de hacer
     falta en cuanto un rótulo entra en su columna. La regla real es la del
     rótulo: si necesita partirse, está mal elegido para el ancho que tiene. */
  overflow-wrap: break-word;
}

/* LV-177: una columna de números se lee comparando, y para comparar las cifras
   tienen que estar alineadas por unidad y medir lo mismo. */
.cell-numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.table-normalized thead th.cell-numeric { text-align: right; }

/* IDENTIDAD: la línea 1 es el identificador con el que se habla del registro
   (código, matrícula, nombre) y no se parte; la línea 2 desambigua y trunca. */
.table-normalized td:first-child { white-space: normal; }
.table-normalized .table-primary-value { white-space: nowrap; }
.table-primary-sub {
  display: block;
  color: var(--ac-text-secondary);
  font-size: 0.78rem;
  line-height: 1.3;
}

/* Numéricos: a la derecha y con cifras de ancho fijo, para que las columnas de
   conteo se comparen en vertical en vez de flotar en una franja vacía. */
.table-normalized .cell-numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.table-normalized th.cell-numeric { text-align: right; }

.table-normalized .cell-status { text-align: center; }
.table-normalized th.cell-status { text-align: center; }

/* Salida definida para el texto largo: con table-layout fijo, sin esto el
   contenido desborda la celda en vez de repartirse. El valor completo queda
   en el title. Sustituye al style="max-width:16rem" inline que había. */
.table-normalized .cell-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── LV-163: la prueba de conocimientos se lee ──────────────────────────────
   Pedido del usuario el 2026-08-27, mirando la pantalla: *"se ve muy apagado y
   plano la lectura"*.

   El diagnóstico no era el color del tema: era que **todo pesaba igual**. La
   pregunta y las cuatro opciones salían en negrita, en MAYÚSCULAS —que vienen
   del banco de la DGAC, no del CSS— y a todo el ancho. Con todo gritando no hay
   jerarquía, y sin jerarquía el ojo no tiene por dónde entrar.

   Las mayúsculas **no se tocan**: el banco lleva METAR, DAN 151, RPA, DGAC, y
   bajarles la caja le cambiaría el sentido a una pregunta de examen (ver
   `assessments._unwrapped`). El remedio es tipográfico. */

/* Las tres reglas como datos y no como una frase corrida: es lo que se quiere
   saber antes de empezar, y en un párrafo gris había que leer toda la línea
   para encontrar el 80%. */
.quiz-rules {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1.75rem;
  padding: 1rem 1.25rem;
  border: 1px solid var(--ac-border);
  border-left: 4px solid var(--ac-primary);
  border-radius: var(--ac-radius);
  background: var(--ac-surface);
}
.quiz-rule { display: flex; flex-direction: column; line-height: 1.1; }
.quiz-rule-value {
  color: var(--ac-primary);
  font-size: 1.6rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.quiz-rule-label {
  color: var(--ac-text-secondary);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
/* LV-172: el total de cierre del catastro. Alineado a la derecha y con el acento
   de la marca, como el total de un documento entregable: es lo último que se
   lee, y se busca al pie. */
.catastro-closing {
  margin: 0 0 1.5rem;
  padding-top: 0.75rem;
  border-top: 2px solid var(--ac-border);
  color: var(--ac-text);
  font-size: 1.05rem;
  font-weight: 700;
  text-align: right;
}

/* LV-171: la hoja de campo de SIGO dibujaba once casillas idénticas -- rótulo y
   valor con el mismo tamaño y el mismo gris, todas con el mismo borde. Con todo
   pesando igual el ojo no tiene por dónde entrar, que es el mismo diagnóstico de
   `LV-163` y no un problema de contraste: **ningún gris se aclaró** (ver
   `LV-165`, que midió que ya cumplían AA y que aclararlos dejaría la pantalla
   más plana todavía). Lo que faltaba era jerarquía y color.

   El valor manda porque es lo que se transcribe casilla por casilla en el
   formulario del Estado; el rótulo sólo dice cuál es cuál, así que se achica y
   se queda quieto. */
.sigo-field {
  height: 100%;
  padding: 0.55rem 0.7rem;
  border: 1px solid var(--ac-border);
  border-left: 3px solid var(--sigo-accent, var(--ac-border));
  border-radius: 8px;
  background: var(--ac-surface-raised);
}

.sigo-field-label {
  color: var(--ac-text-secondary);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.sigo-field-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.15rem;
}

.sigo-field-value {
  color: var(--ac-text);
  font-family: var(--bs-font-monospace, monospace);
  font-size: 1.05rem;
  font-weight: 600;
  /* Los grados, minutos y segundos se leen en columna: sin cifras de ancho fijo
     los números bailan de casilla en casilla y comparar dos cuesta más. */
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* Un guion no es un dato: la casilla vacía se queda quieta en vez de gritar. */
.sigo-field-value.is-empty { color: var(--ac-text-muted); font-weight: 400; }

/* Cuatro acentos para cinco tramos, y la excepción es deliberada: latitud y
   longitud comparten el suyo porque son las dos mitades de una coordenada.
   Los tonos son los que el menú ya usa para separar familias, así que esta
   pantalla no estrena un idioma de color propio. */
.sigo-section-area { --sigo-accent: #0d9488; }
.sigo-section-admin { --sigo-accent: #2463b8; }
.sigo-section-coords { --sigo-accent: #7c3aed; }
.sigo-section-circle { --sigo-accent: #b15d00; }

[data-theme="dark"] .sigo-section-area, [data-bs-theme="dark"] .sigo-section-area { --sigo-accent: #55d6c8; }
[data-theme="dark"] .sigo-section-admin, [data-bs-theme="dark"] .sigo-section-admin { --sigo-accent: #82b1ff; }
[data-theme="dark"] .sigo-section-coords, [data-bs-theme="dark"] .sigo-section-coords { --sigo-accent: #c4a5ff; }
[data-theme="dark"] .sigo-section-circle, [data-bs-theme="dark"] .sigo-section-circle { --sigo-accent: #ffc66d; }

/* El título del tramo lleva el mismo acento en una marca corta a la izquierda:
   así el color dice a qué tramo pertenece cada fila sin repetirlo en cada
   casilla, y quien no distingue esos tonos sigue teniendo el título escrito. */
.sigo-section-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 1.25rem 0 0.5rem;
  color: var(--ac-text-secondary);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.sigo-section-title::before {
  content: "";
  width: 3px;
  height: 0.9rem;
  border-radius: 2px;
  background: var(--sigo-accent, var(--ac-border));
}

.quiz-rules-note {
  flex: 1 1 18rem;
  color: var(--ac-text-secondary);
  font-size: 0.85rem;
}

.quiz-card { padding: 1.25rem 1.4rem; }

/* El número es el ÚNICO elemento pesado de la pregunta. Antes lo eran el número,
   el enunciado y las cuatro opciones a la vez. */
/* LV-168b: el `float` de Bootstrap se **conserva**, y es lo que mete el
   enunciado dentro de la tarjeta. Un `<legend>` sin flotar es el "rendered
   legend" del navegador: se monta sobre el borde superior del `<fieldset>`,
   ignora su relleno y borra el borde por detrás. Medido: sin flotar el
   enunciado arranca a 0 px del borde de la caja; flotado, a los 21 px que le
   corresponden (1 de borde + 20 de relleno). Con una o dos líneas apenas se
   notaba; con las seis de una pregunta enumerada la pregunta se ve suelta,
   fuera del recuadro. `LV-163` había puesto `float: none` para que el flex del
   número anduviera: la salida no era quitar el float sino mudar el flex a
   `.quiz-question-row`, adentro. */
.quiz-question {
  display: block;
  float: left;
  width: 100%;
  /* 2.25rem y no 1rem: el hueco hasta las opciones medía 36 px en la pantalla
     que se aprobó, y 20 de esos los ponía el propio defecto del rendered
     legend. Al corregirlo, un `1rem` literal habría apretado la pregunta
     contra sus opciones sin que nadie lo pidiera. */
  margin-bottom: 2.25rem;
  padding: 0;
  font-size: 1rem;
}
.quiz-question-row {
  display: flex;
  gap: 0.9rem;
  align-items: baseline;
}
.quiz-number {
  flex: 0 0 auto;
  min-width: 1.9rem;
  padding: 0.1rem 0.45rem;
  border-radius: 999px;
  background: var(--ac-primary-soft);
  color: var(--ac-primary);
  font-size: 0.85rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}
/* Peso normal e interlínea holgada. El ancho de lectura se acota: un renglón de
   1500 px en mayúsculas obliga al ojo a barrer de punta a punta y perder la
   línea al volver. */
.quiz-text {
  max-width: 76ch;
  color: var(--ac-text);
  font-weight: 400;
  line-height: 1.55;
}

/* LV-168: cuando el enunciado enumera, cada ítem es un renglón con sangría
   francesa — el número romano queda solo a la izquierda y lo que se desborda se
   alinea bajo el texto, no bajo el número. Sin esto los ítems corren dentro del
   párrafo y hay que rastrear dónde empieza cada uno para poder elegir entre
   "SÓLO I Y II" y "SÓLO II Y III". */
.quiz-text-line { display: block; }

.quiz-text-line + .quiz-text-line {
  margin-top: 0.3rem;
  padding-left: 2.4rem;
  text-indent: -0.9rem;
}

.quiz-options { display: flex; flex-direction: column; gap: 0.45rem; }

/* La opción es superficie clicable con estado propio. Antes el blanco del clic
   era el `radio` de 16 px, y la elegida no se distinguía de un barrido. */
.quiz-option {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
  margin: 0;
  padding: 0.6rem 0.85rem;
  border: 1px solid var(--ac-border);
  border-radius: 10px;
  background: var(--ac-surface-raised);
  cursor: pointer;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}
.quiz-option:hover { border-color: var(--ac-primary); }
/* El `radio` sigue en el DOM —teclado y lector de pantalla— y sale de la vista;
   el estado se dibuja en la fila con el selector **hermano**: el `input` va
   antes de la etiqueta y el `for` mantiene toda la fila como blanco del clic.

   `:has()` haría lo mismo envolviendo el input en la etiqueta. Se prefiere el
   hermano por compatibilidad —anda en navegadores anteriores a `:has()`— y
   nada más: la primera versión lo cambió culpando a `:has()` de que el estado
   no se pintaba, y ése **no** era el motivo. El motivo era que el panel de
   navegador de esta máquina congela las transiciones CSS, así que el color
   nuevo nunca avanzaba desde el de partida; con `transition: none` los dos
   selectores dan el valor correcto. Queda escrito para que nadie vuelva a
   perseguir el fantasma: si un estado no se pinta al verificar acá, sospechar
   de la transición antes que del selector. */
.quiz-option-row { position: relative; }
.quiz-option-input {
  position: absolute;
  top: 50%;
  left: 0.85rem;
  width: 1px;
  height: 1px;
  opacity: 0;
}
.quiz-option-input:focus-visible + .quiz-option {
  outline: 2px solid var(--ac-primary);
  outline-offset: 2px;
}
.quiz-option-input:checked + .quiz-option {
  border-color: var(--ac-primary);
  background: var(--ac-primary-soft);
}
.quiz-option-key {
  flex: 0 0 auto;
  width: 1.6rem;
  height: 1.6rem;
  display: grid;
  place-items: center;
  border: 1px solid var(--ac-border);
  border-radius: 50%;
  background: var(--ac-surface);
  color: var(--ac-text-secondary);
  font-size: 0.78rem;
  font-weight: 700;
}
.quiz-option-input:checked + .quiz-option .quiz-option-key {
  border-color: var(--ac-primary);
  background: var(--ac-primary);
  color: var(--ac-surface);
}
.quiz-option-text {
  color: var(--ac-text);
  font-weight: 400;
  line-height: 1.45;
}

/* El pie se queda a la vista: con 25 preguntas el botón de enviar estaba a
   varias pantallas de scroll del avance, así que las dos cosas que se necesitan
   al final vivían separadas. */
.quiz-footer {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  align-items: center;
  justify-content: space-between;
  margin-top: 1.25rem;
  padding: 0.85rem 1.1rem;
  border: 1px solid var(--ac-border);
  border-radius: var(--ac-radius);
  background: var(--ac-surface);
  box-shadow: var(--ac-shadow);
}
.quiz-progress { display: flex; gap: 0.75rem; align-items: center; min-width: 14rem; }
.quiz-progress-bar {
  flex: 1 1 8rem;
  height: 6px;
  border-radius: 999px;
  background: var(--ac-border);
  overflow: hidden;
}
.quiz-progress-fill {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: var(--ac-primary);
  transition: width 0.15s ease;
}
.quiz-progress-text {
  color: var(--ac-text-secondary);
  font-size: 0.85rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* Completa: el color dice "se puede enviar" sin agregar una frase más. */
.quiz-footer.is-complete .quiz-progress-text { color: var(--ac-primary); font-weight: 700; }

@media (max-width: 640px) {
  .quiz-rules { gap: 1.1rem; }
  .quiz-footer { flex-direction: column; align-items: stretch; }
}

/* == UX-06 · Impresión ======================================================
 *
 * Criterio de la fila: imprimir un informe no incluye menú, barra ni botones;
 * las tablas no se cortan a mitad de fila; sale el sello de generación.
 *
 * Va acá y no en cada pantalla porque el problema no era de una: el árbol
 * tenía **cero** reglas `@media print`, así que toda impresión salía con la
 * navegación entera encima y el contenido estrujado en la columna que le
 * dejaba la barra lateral.
 *
 * No se fija `@page` acá a propósito: los márgenes por omisión de la impresora
 * son los correctos para una pantalla normal. La única que los quiere en cero
 * es la hoja A4 del informe mensual, y eso lo declara `report-a4.css`, que se
 * carga sólo en esa pantalla.
 */

/* == UX-04 · Un solo tamaño de encabezado de página =========================
 *
 * Había **ocho formas** de escribir el `<h1>` repartidas en 59 apariciones, así
 * que el título cambiaba de tamaño al navegar. La regla va sobre `main h1` y no
 * sólo sobre `.page-title` **a propósito**: la clase la ponen las plantillas ya
 * migradas al parcial, y hasta que estén todas, el resto seguiría con su
 * `h3`/`h4` de Bootstrap. Con el selector sobre el elemento, la pantalla queda
 * pareja hoy y la migración del marcado puede ir plantilla por plantilla sin
 * dejar un tamaño distinto en el medio.
 *
 * `main` acota el alcance: el `<h1>` del acceso y el de la pantalla de bloqueo
 * viven fuera del armazón —tienen su propia chapa y su propio tamaño— y no son
 * encabezados de sección.
 *
 * ⚠️ **`main h1[class]` no es adorno de especificidad: sin él la regla no hace
 * nada en 26 de las 59.** `main h1` vale (0,0,2) y la `.h3` de Bootstrap vale
 * (0,1,0), así que Bootstrap gana y el orden de las hojas no ayuda cuando la
 * especificidad difiere. Medido en el navegador **después** de escribir la
 * regla: los `h1.h3` seguían en 24,37 px mientras los pelados iban a 30,93. El
 * selector de atributo sube a (0,1,2) y sí gana. Es "medir y además mirar" en
 * su forma más barata — el CSS parecía correcto y no movía un píxel. */
main h1,
main h1[class],
.page-title {
  font-size: var(--fs-xl);
  font-weight: 700;
  line-height: 1.2;
}

/* == UX-11 · La barra de la selección ======================================
 *
 * Aparece **sólo con algo marcado** y dice cuántos. Una barra permanente que
 * dice "0 seleccionados" ocupa sitio todos los días para avisar de nada.
 *
 * Pegajosa arriba: la selección se hace recorriendo la lista hacia abajo, y una
 * barra que se va con el scroll obliga a volver para usarla. */
.bulk-bar {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  margin-bottom: var(--sp-4);
  padding: var(--sp-3) var(--sp-4);
  border: 1px solid var(--ac-primary);
  border-radius: 8px;
  background: var(--ac-primary-soft);
  color: var(--ac-text);
}
.bulk-count { font-weight: 600; }
[data-theme="dark"] .bulk-bar {
  background: var(--ac-surface-raised);
  border-color: var(--ac-primary);
}

/* == UX-07 · La tabla de trabajo ===========================================
 *
 * Encabezado que ordena. El enlace ocupa la celda entera y no sólo el texto:
 * un objetivo de clic del tamaño de la palabra "Creado" es de los que se
 * fallan, y en táctil se falla siempre.
 *
 * La marca de orden va **siempre**, incluso sin ordenar (con la flecha doble),
 * porque es lo que dice que la columna se puede ordenar. Dibujarla sólo al
 * ordenar convierte la función en algo que hay que descubrir por accidente. */
.worktable-sort {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-2);
  color: inherit;
  text-decoration: none;
}
.worktable-sort:hover { color: var(--ac-primary); }
.worktable-sort:hover .worktable-sort-mark { opacity: 1; }
/* Atenuada mientras no ordena, para que en una tabla de siete columnas las
 * flechas no compitan con los rótulos. La que está activa va entera. */
/* LV-259: 0,35 dejaba la marca en 1,69 : 1 en claro y 2,59 en oscuro — por
 * debajo del 3 : 1 que pide un indicador de interfaz. A 0,7 sigue más tenue que
 * el rótulo, que es lo que buscaba el comentario de arriba, y se ve. */
.worktable-sort-mark { opacity: 0.7; font-size: 0.75em; }
.worktable-sort.is-sorted { color: var(--ac-primary); font-weight: 600; }
.worktable-sort.is-sorted .worktable-sort-mark { opacity: 1; }

/* El borde de severidad, en la fila y no en una insignia suelta.
 *
 * `UX-01` dejó los cinco niveles como tokens; acá se usan para teñir el canto
 * izquierdo de la fila. Es la única señal que se lee **sin leer**: al recorrer
 * cincuenta permisos, lo vencido salta antes de que el ojo llegue a la columna
 * de fecha. Se pinta con `box-shadow` en la primera celda y no con
 * `border-left` en el `<tr>`, porque un borde en la fila no se dibuja con
 * `border-collapse` y quedaría invisible justo en las tablas del proyecto. */
tr[data-sev] > td:first-child {
  box-shadow: inset 3px 0 0 0 transparent;
}
/* LV-261: la segunda sombra es el realce de `.table-hover`, que la barra del
   canto reemplazaba — sin ella, al pasar el mouse la primera celda era la única
   de la fila que no se realzaba. */
tr[data-sev="critical"] > td:first-child { box-shadow: inset 3px 0 0 0 var(--sev-critical-border), inset 0 0 0 9999px var(--bs-table-bg-state, transparent); }
tr[data-sev="warning"] > td:first-child { box-shadow: inset 3px 0 0 0 var(--sev-warning-border), inset 0 0 0 9999px var(--bs-table-bg-state, transparent); }
tr[data-sev="caution"] > td:first-child { box-shadow: inset 3px 0 0 0 var(--sev-caution-border), inset 0 0 0 9999px var(--bs-table-bg-state, transparent); }
tr[data-sev="advisory"] > td:first-child { box-shadow: inset 3px 0 0 0 var(--sev-advisory-border), inset 0 0 0 9999px var(--bs-table-bg-state, transparent); }

/* == UX-09 / UX-12 · Columnas por persona y vistas guardadas ===============
 *
 * El cuerpo espera escondido **sólo cuando hay columnas que esconder**, para no
 * mostrar la tabla entera y sacarle columnas encima. Es la misma razón por la
 * que el tema se aplica antes de pintar; y como es condicional, la enorme
 * mayoría de las cargas —nadie escondió nada— no depende de que el JS corra
 * para ver la tabla. */
.wt-pending tbody { visibility: hidden; }

.worktable-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
  margin-bottom: var(--sp-3);
}
.worktable-views { display: flex; align-items: center; gap: var(--sp-2); flex-wrap: wrap; }
.worktable-view-wrap { display: inline-flex; align-items: center; }
.worktable-view {
  padding: var(--sp-2) var(--sp-3);
  border: 1px solid transparent;
  border-radius: 999px;
  color: var(--ac-text-secondary);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
}
.worktable-view:hover { color: var(--ac-primary); background: var(--ac-primary-soft); }
.worktable-view.is-active {
  /* LV-259: el primario sobre su propio fondo suave daba 4,23 : 1 en claro. El
   * mismo arreglo que `.view-tab.is-active`: tono de *hover* en claro, primario
   * en oscuro. */
  color: var(--ac-primary-hover);
  border-color: var(--ac-primary);
  background: var(--ac-primary-soft);
}
[data-theme="dark"] .worktable-view.is-active,
[data-bs-theme="dark"] .worktable-view.is-active { color: var(--ac-primary); }
/* El rombo dice "compartida". Va como marca y no como palabra porque la
 * pestaña ya lleva el nombre que alguien eligió, y sumarle "(compartida)"
 * duplicaría el largo de cada una. */
.worktable-view-shared { opacity: 0.65; font-size: 0.8em; }
.worktable-view-delete {
  border: 0;
  background: none;
  color: var(--ac-text-secondary);
  line-height: 1;
  padding: 0 var(--sp-2);
  cursor: pointer;
}
.worktable-view-delete:hover { color: var(--sev-critical-text); }

.worktable-toolbar-actions { display: flex; gap: var(--sp-2); }
/* `<details>` como menú: sin JS para abrirlo y cerrarlo, y con el foco que el
 * navegador ya gestiona. El marcador propio se saca porque el resumen es un
 * botón, y un triangulito dentro de un botón se lee como dos controles. */
.worktable-columns summary::-webkit-details-marker,
.worktable-save-view summary::-webkit-details-marker { display: none; }
.worktable-columns summary,
.worktable-save-view summary { list-style: none; cursor: pointer; }
.worktable-columns,
.worktable-save-view { position: relative; }
.worktable-columns-menu,
.worktable-save-menu {
  position: absolute;
  right: 0;
  z-index: 4;
  min-width: 240px;
  margin-top: var(--sp-2);
  padding: var(--sp-3);
  border: 1px solid var(--ac-border);
  border-radius: 8px;
  background: var(--ac-surface);
  box-shadow: 0 8px 24px rgb(0 0 0 / 12%);
}
.worktable-column-option { margin-bottom: var(--sp-2); }

/* La columna de casillas no necesita ancho: encoge a su contenido. */
.bulk-cell { margin: 0; }
.table th:has(> .bulk-cell),
.table td:has(> .bulk-cell) { width: 1%; }

/* == UX-08 · Densidad conmutable ===========================================
 *
 * Cómoda (por omisión) y compacta. La diferencia real es cuántas filas caben
 * sin desplazar: en una lista de veinte permisos, pasar de 40 a 32 px por fila
 * son cuatro filas más a la vista — y quien revisa vencimientos mira la lista
 * entera, no una fila.
 *
 * El atributo lo pone `theme-init.js` **antes de pintar**, por lo mismo que el
 * tema: aplicarlo después haría saltar la lista a la vista.
 *
 * Sólo toca el relleno. Bajar además el cuerpo de letra habría hecho que
 * "compacta" significara también "más difícil de leer", que es otra decisión y
 * no la que esta fila ofrece. */
[data-density="compact"] .table > :not(caption) > * > * {
  padding-top: var(--sp-2);
  padding-bottom: var(--sp-2);
}
[data-density="compact"] .table td,
[data-density="compact"] .table th { line-height: 1.3; }

/* En pantalla angosta la fila ya no es una fila: es una tarjeta apilada
 * (`UX-10`), y ahí la densidad no aplica — comprimir el relleno vertical
 * juntaría los rótulos hasta hacerlos ilegibles. */
@media (max-width: 767.98px) {
  [data-density="compact"] .table-responsive .table > :not(caption) > * > * {
    padding-top: var(--sp-1);
    padding-bottom: var(--sp-1);
  }
}

/* == UX-10 · La fila se apila como tarjeta bajo 768 px =====================
 *
 * Medido: la lista de aeronaves mide **900 px de tabla dentro de un contenedor
 * de 356** en una pantalla de 390. Leer una fila en faena obliga a arrastrar de
 * lado, con guantes, y volver a empezar en la siguiente.
 *
 * El rótulo de cada celda lo pone `worktable.js` leyéndolo del `<th>`, así que
 * no hay que escribir `data-label` en las 58 tablas ni mantener el encabezado
 * duplicado en dos sitios. Acá sólo se dibuja.
 *
 * El encabezado se oculta con `position: absolute` y no con `display: none`:
 * `display:none` lo saca del árbol de accesibilidad, y un lector de pantalla
 * perdería los nombres de columna que la vista sí sigue usando. */
@media (max-width: 767.98px) {
  /* ⚠️ **Esto revierte una decisión deliberada de `LV-162`, y conviene decirlo.**
   * Esa fila puso `min-width: 900px` en `.table-normalized` con el motivo
   * escrito: *"con anchos en porcentaje, una pantalla estrecha aplastaría las
   * columnas hasta hacerlas ilegibles; por debajo de esto scrollea"*. Era la
   * elección correcta **cuando la única alternativa era aplastar**. Con la fila
   * apilada como tarjeta ya no hay columnas que aplastar, así que el mínimo
   * dejó de proteger algo y sólo obliga a arrastrar de lado: 900 px de tabla en
   * un contenedor de 356, medido. */
  .table-responsive .table-normalized {
    min-width: 0;
    table-layout: auto;
  }

  .table-responsive table,
  .table-responsive tbody,
  .table-responsive tr,
  .table-responsive td { display: block; width: auto; }

  /* El `<colgroup>` reparte anchos entre columnas que ya no existen como tales. */
  .table-responsive colgroup { display: none; }

  .table-responsive thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Cada fila es una tarjeta con su propio borde: sin él, veinte filas
   * apiladas se leen como un solo bloque de texto. */
  .table-responsive tbody tr {
    border: 1px solid var(--ac-border);
    border-radius: 8px;
    margin-bottom: var(--sp-4);
    padding: var(--sp-3);
    background: var(--ac-surface);
  }

  .table-responsive tbody td {
    border: 0;
    padding: var(--sp-1) 0;
    display: flex;
    gap: var(--sp-3);
    align-items: baseline;
    /* ⚠️ Los tres juntos, y ninguno sobra. Un ítem flex trae
     * `min-width: auto`, así que **no encoge por debajo de su contenido**: sin
     * esto la celda de Seguro medía 447 px en un contenedor de 356 y la tabla
     * seguía desbordando aunque las filas ya fueran bloques. Medido. */
    flex-wrap: wrap;
    min-width: 0;
    overflow-wrap: anywhere;
  }
  .table-responsive tbody td > * { min-width: 0; }

  /* El nombre de la columna, delante del dato. Ancho fijo para que los valores
   * queden alineados entre sí dentro de la tarjeta. */
  .table-responsive tbody td[data-label]::before {
    content: attr(data-label);
    flex: 0 0 40%;
    font-size: var(--fs-xs);
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--ac-text-muted);
    /* Las columnas numéricas llevan `.text-end`, y el rótulo lo heredaba: en la
     * tarjeta quedaban unos pegados a la izquierda y otros a la derecha, que es
     * lo contrario de una columna de rótulos que se barre de un vistazo. */
    text-align: start;
  }

  /* La celda de acciones no lleva rótulo (ver `worktable.js`) y ocupa el ancho
   * entero, debajo de los datos. */
  .table-responsive tbody td:not([data-label]) {
    justify-content: flex-end;
    padding-top: var(--sp-3);
  }

  /* Las filas de agrupación y la de "sin resultados" abarcan varias columnas:
   * no son tarjetas, son separadores. */
  .table-responsive tbody tr:has(td[colspan]),
  .table-responsive tbody tr:has(th[colspan]) {
    border: 0;
    padding: 0;
    margin-bottom: var(--sp-2);
  }
}

/* El sello sólo existe en papel: en pantalla la fecha y la URL están a la
 * vista, y repetirlas abajo de cada página sería ruido. */
.print-stamp { display: none; }

@media print {
  /* La navegación, los diálogos y los controles no son el documento. */
  .navbar,
  .sidebar,
  .sidebar-resizer,
  .skip-link,
  .modal,
  .modal-backdrop,
  .btn,
  .btn-close,
  .pagination,
  .d-print-none { display: none !important; }

  /* Sin la barra lateral, el contenido ocupa la hoja. */
  body { background: #ffffff; }
  .container-fluid,
  main { display: block !important; width: auto !important; max-width: none !important; margin: 0 !important; padding: 0 !important; }

  /* El contenedor que hace scroll en pantalla recorta la tabla en papel. */
  .table-responsive { overflow: visible !important; }

  /* Ninguna fila partida entre dos páginas, y el encabezado repetido arriba de
   * cada una: una tabla larga sin esto deja la segunda página sin decir qué
   * columna es cuál. */
  table { break-inside: auto; width: 100% !important; }
  thead { display: table-header-group; }
  tfoot { display: table-footer-group; }
  tr, .card, .summary-card { break-inside: avoid; }

  /* Las insignias de estado son información: sin esto el navegador descarta su
   * fondo y una fila "Vencido" sale igual que una "Vigente". */
  .badge,
  [class*="sev-"] { print-color-adjust: exact; -webkit-print-color-adjust: exact; }

  /* El sello: qué se imprimió, de dónde y cuándo. Un papel sin fecha de
   * generación no se puede contrastar contra la pantalla de nadie. */
  .print-stamp {
    display: block !important;
    margin-top: 1rem;
    padding-top: 0.5rem;
    border-top: 1px solid #999999;
    font-size: 9px;
    color: #555555;
  }
}
