/* =============================================================================
   VulnTracker — hoja de estilos única
   =============================================================================
   Implementa la Guía de Estilo UI de la familia (ver GUIA-ESTILO-UI.md):
   dashboard corporativo fintech-seguridad, fondo crema, superficies blancas,
   estructura en azul marino y acción/marca en naranja. Tipografía Inter,
   etiquetas en MAYÚSCULAS con tracking, iconografía lineal tipo Feather.

   CSS plano con variables. Sin Bootstrap, sin Tailwind, sin build step: este
   archivo se sirve tal cual desde public/css/.

   Orden: tokens · reset y tipografía · layout · barra lateral · módulos ·
   botones · formularios · tablas · badges · gráficos · pantallas concretas ·
   esqueleto de carga · exportación del informe · responsive.
   ============================================================================= */


/* ============================================================================
   1. DESIGN TOKENS  (§2 de la guía)
   ========================================================================== */
/* Toast del kit de fluidez: en escritorio no hay barra superior, así que va
   pegado arriba. En móvil (movil.css) baja para no tapar la barra. */
:root { --ux-toast-top: 20px; }

:root {
  /* Azules — estructura, headers, texto de marca */
  --navy:       #192F50;
  --navy-mid:   #264168;
  --navy-light: #304F7B;

  /* Naranjas / dorado — marca, acción, foco, énfasis */
  --gold:       #FF8300;
  --gold-light: #FFA83D;

  /* Letra sobre cada color de marca: botones, barra lateral, cabeceras de
     módulo, distintivo. Blanco o negro y nada más, lo elige la empresa en su
     pantalla de marca junto con los dos colores (layouts/app.php los reescribe).
     Los grises translúcidos de la barra —etiquetas, iconos, rayas— salen de
     --navy-on con color-mix, así que sobre una lateral clara se oscurecen solos
     en vez de quedar blancos sobre blanco. */
  --navy-on: #fff;
  --gold-on: #fff;

  /* Neutros */
  --cream:  #f5f0e8;
  --white:  #fff;
  --gray:   #e8e4dc;
  --text:   #1a1a2e;
  --sub:    #5a6070;

  /* ── Semánticos (feedback) ────────────────────────────────────────────
     Tres papeles con tres nombres, que es lo que el estándar común (§3.2) fija
     para las siete apps. Antes había uno solo por color y la app acabó
     fabricándose los otros dos a mano: el verde de «bien» estaba escrito dos
     veces (#1a7a42 y #1e7a45, a 0,03 de contraste uno del otro) y el rojo de
     letra sobre claro (#c0392b) otras dos, sin nombre ninguno de los cuatro.

     --ok/--warn/--err : letra y filo sobre fondo claro. Son los que se usan por
                         defecto. Sobre blanco: 4,72 · 3,06 · 5,44. El ámbar no
                         llega a AA, pero es el más alto de los cuatro ámbares
                         de la suite y el estándar lo fija así.
     --*-fill          : relleno — píldoras, puntos, barras, fondos saturados.
                         Nunca como texto: sobre blanco dan 2,10 / 2,85 / 3,82.
     --err-line        : el rojo del filo sobre superficie navy, donde el oscuro
                         no contrasta. Es el mismo valor y el mismo nombre que
                         en `rat`. */
  --ok:   #1e8449;
  --warn: #d1810c;
  --err:  #c0392b;

  --ok-fill:   #2ecc71;
  --warn-fill: #e67e22;
  --err-fill:  #e74c3c;

  --err-line: #e74c3c;

  /* ── Campos de formulario (§3.3 del estándar común) ───────────────────
     El kit de campos deja de estar escrito a mano. --field-border vale hoy lo
     mismo que --gray a propósito: son dos tokens distintos para que el
     formulario se pueda mover sin arrastrar consigo los neutros de la app. */
  --field-bg:     #fafaf8;
  --field-border: #e8e4dc;
  --field-focus:  var(--gold);

  /* Radio de esquina base */
  --r: 6px;

  /* Barra lateral: ancho desplegada y contraída (§6) */
  --sb-w:     264px;
  --sb-w-min: 76px;

  /* Neutros del esqueleto de carga (§13). Son los de la familia: sólo faltaba
     declararlos. No usar para nada más. */
  --cream-2: #efe9de;
  --gray-2:  #d9d3c7;

  /* ── Paleta de riesgo (tomada del informe) ─────────────────────────────
     Estos cuatro colores salen literalmente del .docx que se entrega al
     cliente (sus celdas los traen fijos en el XML). Son los únicos del sistema
     que NO vienen de la guía de estilo ni se personalizan por empresa: si la
     plataforma pintara un «Alto» de un rojo y el Word de otro, el cliente
     vería dos cosas distintas para el mismo dato.

     Dos sorprenden y son correctas: «Bajo» es celeste y no verde, y «Alto» es
     rojo puro mientras «Crítico» es un rojo más oscuro — se distinguen por
     luminosidad, no por tono.

     -on   = texto sobre el relleno lleno (el ámbar y el celeste piden negro)
     -ink  = el mismo color oscurecido, para letra sobre fondo claro
     -soft = fondo suave, para las superficies que no van rellenas */
  --sev-critical:        #C00000;
  --sev-critical-on:     #FFFFFF;
  --sev-critical-ink:    #A50000;
  --sev-critical-soft:   rgba(192, 0, 0, .10);
  --sev-critical-border: rgba(192, 0, 0, .32);

  --sev-high:        #FF0000;
  --sev-high-on:     #FFFFFF;
  --sev-high-ink:    #D10000;
  --sev-high-soft:   rgba(255, 0, 0, .10);
  --sev-high-border: rgba(255, 0, 0, .30);

  --sev-medium:        #FFC000;
  --sev-medium-on:     #1A1A2E;
  --sev-medium-ink:    #9A6E00;
  --sev-medium-soft:   rgba(255, 192, 0, .18);
  --sev-medium-border: rgba(255, 192, 0, .45);

  --sev-low:        #00B0F0;
  --sev-low-on:     #1A1A2E;
  --sev-low-ink:    #00688F;
  --sev-low-soft:   rgba(0, 176, 240, .14);
  --sev-low-border: rgba(0, 176, 240, .38);

  /* El informe no tiene nivel informativo; se usa el gris de «no aplica». */
  --sev-info:        #7F8C8D;
  --sev-info-on:     #FFFFFF;
  --sev-info-ink:    #5A6070;
  --sev-info-soft:   rgba(127, 140, 141, .12);
  --sev-info-border: rgba(127, 140, 141, .30);

  /* Cabecera de las tablas de riesgo del informe. */
  --risk-head:      #002060;
  --risk-head-soft: #F3F3F3;

  --status-new:             #2980b9;
  --status-new-soft:        rgba(52, 152, 219, .12);
  --status-persistent:      #7f8c8d;
  --status-persistent-soft: rgba(127, 140, 141, .12);
  --status-resolved:        #27ae60;
  --status-resolved-soft:   rgba(46, 204, 113, .12);
  /* «Riesgo aceptado»: es un hallazgo real y vigente con el que se decidió
     convivir, así que no puede verse como resuelto ni como pendiente. Es el
     cuarto estado y se añadió después; vivía al final del archivo, fuera de
     este bloque. */
  --status-accepted:        #8a4700;
  --status-accepted-soft:   rgba(230, 126, 34, .12);
}

/* ============================================================================
   2. RESET Y TIPOGRAFÍA  (§3, §6)
   ========================================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  background: var(--cream);
  color: var(--text);
  min-height: 100vh;
  font-size: 13px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

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

/* Lo que pinta el navegador también lleva la paleta: el texto seleccionado
   se tiñe de dorado suave en vez del azul del sistema, el cursor de escritura
   es dorado como el borde del campo enfocado, y las casillas nativas que no
   declaran el suyo usan el navy. Las que ya lo eligen (.on-opt, .sql-check)
   lo conservan. */
::selection { background: color-mix(in srgb, var(--gold) 22%, transparent); }
:root { accent-color: var(--navy); caret-color: var(--gold); }

/* Escala tipográfica del estándar común (§4). Ningún font-size fuera de ella:
   10 · 11 · 12 · 13 · 14 · 15 · 16 · 18 · 21 · 24 · 30 · 36 · 44 */

/* Firma tipográfica de la familia: toda etiqueta, título de sección y
   encabezado de tabla se ve así. */
.lbl,
.form-label,
.card-title,
.sb-title,
.sb-role,
.filters .fl label,
.data-table thead th,
.kpi-label,
.score-label,
.aging-label {
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--sub);
}

.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.tnum { font-variant-numeric: tabular-nums; }

/* Cifras de ancho fijo donde se leen y comparan números. Con las cifras
   proporcionales de Inter un «1» ocupa la mitad que un «0»: las columnas de
   una tabla no calzan ("1 → 1" bajo "3 → 1"), el score y los contadores
   cambian de ancho al cambiar de informe y las fechas de una lista quedan
   en zigzag. Ya lo llevaban las barras, el score de los hallazgos y la
   antigüedad; aquí se suma lo que faltaba. Solo cambia el ancho de los
   dígitos, no el texto que los acompaña. */
.data-table,
.pagination,
.module-cnt,
.module-sub,
.hero-fig .val,
.hero-fig .delta,
.fig-card .val,
.cmp-delta,
.donut-hole .val,
.legend-val,
.legend-pct,
.mttr-value,
.aging-value,
.sum-card .val,
.proj-val,
.inf-cnt,
.cl-row-det,
.user-row-meta,
.rep-pick,
.chip,
.fl input,
.form-input {
  font-variant-numeric: tabular-nums;
}

/* Cortes de línea. Los títulos reparten sus palabras en líneas parecidas en
   vez de dejar una sola colgando ("El riesgo bajó 7% respecto a / julio"), y
   los textos de apoyo evitan la viuda de una palabra al final. Si el
   navegador no lo entiende, corta como siempre. */
.hero-title,
.module-title,
.detail-title,
.blk-title,
.l-title,
.empty-state strong,
.ux-dialogo-titulo {
  text-wrap: balance;
}
.hero-text,
.module-sub,
.card-sub,
.blk-sub,
.detail-desc,
.empty-state,
.form-help,
.form-error,
.gbar-note,
.inf-nota,
.l-sub,
.note-box,
.sql-note,
.err-box,
.one-act-text,
.plan-sub,
.ux-dialogo-texto {
  text-wrap: pretty;
}

/* Notas que acompañan a una cifra: bajan enteras de línea si no caben, en
   vez de dejar "−5 vs" arriba y "julio" sola abajo. */
.hero-fig .delta,
.mttr-unit,
.proj-unit {
  white-space: nowrap;
}
.ellipsis { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.muted { color: var(--sub); }
.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* Iconos SVG lineales tipo Feather (§9): heredan color y escalan con el texto. */
.ic { display: inline-block; vertical-align: -.125em; flex-shrink: 0; }

/* ============================================================================
   3. LAYOUT: barra lateral fija + contenido  (§6)
   ========================================================================== */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--navy); color: var(--navy-on);
  padding: 10px 16px; border-radius: 0 0 var(--r) 0;
  font-size: 12px; font-weight: 700; text-decoration: none;
}
.skip-link:focus { left: 0; }

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

.app-main {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column;
  margin-left: var(--sb-w);
  transition: margin-left .18s ease;
}

/* Zona de contenido. Las vistas no traen su propio contenedor con padding: acá
   están el margen y la separación entre bloques.

   El `margin:auto` es lo que centra la columna dentro de .app-main: con el
   max-width a secas, al ser un ítem de un flex en columna, se pegaba al borde
   izquierdo y en un monitor ancho quedaba media pantalla muerta a la derecha.
   Es el mismo centrado que .pick-page ya hacía en la pantalla de elegir
   empresa. En el eje vertical no hace nada: el `flex:1` se queda con todo el
   espacio libre antes de que los márgenes automáticos puedan repartirlo. */
.page {
  flex: 1; width: 100%; max-width: 1400px; margin: auto;
  padding: 28px 32px 40px;
  display: flex; flex-direction: column; gap: 18px;
}


/* ── Migas de pan y barra de página ───────────────────────────────────────
   El rastro existía ya en dos apps de la suite con dos nombres: `.crumbs` en
   Control-de-Horas (el contenedor) y `.crumb` en rat (también el contenedor,
   con `strong` para el último). Acá se toma el contenedor de Control-de-Horas
   y las piezas sueltas de los dos, que es lo que hace falta para que la regla
   2 del estándar común —un nombre, el mismo en las siete apps— se pueda
   cumplir sin que ninguna de las dos pierda su marcado.

   Tamaño 12 y no 14: el `.875rem` de Control-de-Horas son 14 px sobre un
   cuerpo de 16, y acá el cuerpo es 13. */
.crumbs {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  font-size: 12px; color: var(--sub);
  min-width: 0;
}
.crumbs a { color: var(--sub); text-decoration: none; }
.crumbs a:hover { color: var(--gold); }
.crumb-sep { color: var(--gray-2); }
.crumb-current { color: var(--navy); font-weight: 700; }

/* Primera línea de la página: el rastro a la izquierda y los mandos que
   valen para la pantalla entera a la derecha (el selector de informe del
   resumen). Va fuera de toda tarjeta, sobre la crema. */
/* Un envoltorio dentro de .page —el bloque que reemplaza el filtrado sin
   recarga, como #js-resumen— es un solo hijo para su gap: sin esto los
   bloques de adentro quedan pegados. También cuando lleva el esqueleto, que
   entra como un .sk con las cajas adentro. */
.page-stack, .page-stack > .sk { display: flex; flex-direction: column; gap: 18px; }

.page-top {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  min-height: 34px;
}
.page-top-acts { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

/* Selector de informe. Es un formulario con su botón —sin JavaScript un
   <select> no navega solo—, dibujado como una sola pieza para que no parezca
   un filtro suelto encima de la página. */
.rep-pick {
  display: flex; align-items: center; gap: 9px;
  background: var(--white);
  border: 1px solid var(--gray); border-radius: var(--r);
  padding: 5px 6px 5px 13px;
}
.rep-pick .lbl { margin: 0; }
.rep-pick strong { font-size: 12px; font-weight: 700; color: var(--navy); }
.rep-pick.is-fixed { padding: 9px 13px; }
.rep-pick select {
  appearance: none; -webkit-appearance: none;
  border: 0; background: transparent;
  padding: 6px 26px 6px 0;
  font-size: 12px; font-weight: 700; color: var(--navy);
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235a6070' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 11px;
}
.rep-pick select:focus { outline: 2px solid var(--field-focus); outline-offset: 1px; }

/* ============================================================================
   4. BARRA LATERAL  (§7.1)
   ==========================================================================
   Panel navy fijo a la izquierda, del alto de la ventana, que se contrae a sólo
   iconos. Es la estructura común de la familia de aplicaciones (la misma que
   Control de Horas): sustituye al header sticky + panel blanco que tenía antes
   esta app, y con ella el header desaparece — marca, empresa activa y usuario
   viven ahora en la barra.
   ========================================================================== */
.sidebar {
  position: fixed; top: 0; left: 0; z-index: 60;
  width: var(--sb-w); height: 100vh;
  overflow-y: auto; overflow-x: hidden;
  display: flex; flex-direction: column; gap: 18px;
  padding: 22px 14px 20px;
  background: var(--navy);
  box-shadow: 0 10px 40px rgba(25, 47, 80, .22);
  transition: width .18s ease;
  scrollbar-width: thin;
  scrollbar-color: var(--navy-light) transparent;
}

/* Contraída quedan sólo los iconos, que siguen siendo enlaces. La clase vive en
   <html> y la escribe el script del <head>, antes del primer pintado, para que
   la barra no salte al cargar. */
.sb-collapsed .sidebar  { width: var(--sb-w-min); }
.sb-collapsed .app-main { margin-left: var(--sb-w-min); }
.sb-collapsed .sb-label { display: none; }

/* ── Marca ───────────────────────────────────────────────────────────────── */
.sidebar-top { display: flex; align-items: center; gap: 8px; padding: 0 6px 2px; }
.sidebar-brand {
  display: flex; align-items: center; gap: 11px;
  flex: 1; min-width: 0;
  text-decoration: none; white-space: nowrap;
}
.app-title { font-size: 16px; font-weight: 700; color: var(--navy-on); }
.app-title span { color: var(--gold); }
.app-badge {
  background: var(--gold); color: var(--gold-on);
  font-size: 13px; font-weight: 800; letter-spacing: 2px; text-transform: uppercase;
  padding: 6px 11px; border-radius: 2px;
  flex-shrink: 0;
}
/* Logo de la empresa cuando lo hay (§16): sustituye al cuadradito de marca. */
.app-logo {
  height: 38px; max-width: 100%; object-fit: contain;
  background: #fff; border-radius: 2px; padding: 3px 6px;
  flex-shrink: 0;
}
/* Con logo la marca se apila: al lado del texto un logotipo apaisado no entra
   en el ancho de la barra. Así sirve igual un isotipo cuadrado que una firma
   larga, sin tener que recortar "VulnTracker". */
.sidebar-brand.con-logo { flex-direction: column; align-items: center; gap: 8px; width: 100%; }
/* El botón de contraer sale del flujo para que la marca quede centrada contra
   la barra entera y no contra el hueco que le sobra. */
.sidebar-top:has(.con-logo) { position: relative; }
.sidebar-top:has(.con-logo) .sidebar-toggle { position: absolute; top: 0; right: 0; }
/* Contraída no hay esquina libre donde ponerlo sin taparlo con el logo: vuelve
   al flujo, debajo, o no habría forma de volver a expandir. */
.sb-collapsed .sidebar-top:has(.con-logo),
.sb-collapsed .sidebar-top:has(.con-logo) .sidebar-toggle { position: static; }
/* Contraída no hay ancho para una firma apaisada: se recorta a un cuadrado por
   la izquierda, que es donde casi siempre vive el isotipo. */
.sb-collapsed .app-logo {
  width: 34px; height: 34px; max-width: none; padding: 2px;
  object-fit: cover; object-position: left center;
}
.sb-collapsed .app-badge { padding: 6px 8px; letter-spacing: 1px; }
.sb-collapsed .sidebar-brand.con-logo { gap: 0; }

.sidebar-toggle {
  flex: 0 0 auto; width: 28px; height: 28px;
  display: grid; place-items: center;
  border: 0; border-radius: 7px;
  background: transparent; color: color-mix(in srgb, var(--navy-on) 45%, transparent);
  font-size: 14px; cursor: pointer;
  /* El giro acompaña al cambio de ancho de la barra: la flecha da media
     vuelta mientras se pliega, en vez de aparecer ya girada al final. */
  transition: background .2s, color .2s, transform .18s ease;
}
.sidebar-toggle:hover { background: var(--navy-mid); color: var(--navy-on); }
.sb-collapsed .sidebar-toggle { transform: rotate(180deg); }
/* Contraída no caben marca y botón en la misma línea: se apilan centrados. */
.sb-collapsed .sidebar-top   { flex-direction: column; gap: 10px; padding: 0; }
.sb-collapsed .sidebar-brand { flex: 0 0 auto; justify-content: center; }

/* ── Navegación ──────────────────────────────────────────────────────────── */
.sidebar-nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }
/* Sobre el navy la etiqueta de grupo no puede quedarse con el --sub de .lbl. */
/* 55 % y no 34 %: con 34 la etiqueta de 10 px quedaba en 2,9:1 sobre el
   navy, bajo el 4,5:1 de AA. Con 55 llega a 5,1 y sigue un paso por debajo
   de los enlaces (72 %), que es la jerarquía que importa. Mismo arreglo en
   el rol del pie y en los subtítulos de las cabeceras de módulo. */
.sidebar-nav .sb-title { padding: 14px 12px 6px; letter-spacing: 2px; color: color-mix(in srgb, var(--navy-on) 55%, transparent); }
.sidebar-nav .sb-title:first-child { padding-top: 0; }
/* Contraída desaparecen los títulos de grupo y una raya los sustituye: sin ella
   los iconos de los dos grupos quedarían en una lista corrida. */
.sb-sep { display: none; height: 1px; margin: 10px 8px; background: color-mix(in srgb, var(--navy-on) 9%, transparent); }
.sb-collapsed .sb-sep { display: block; }

.sidebar-link {
  position: relative;
  display: flex; align-items: center; gap: 13px;
  padding: 10px 12px; border-radius: 9px;
  color: color-mix(in srgb, var(--navy-on) 72%, transparent);
  font-size: 12px; font-weight: 500;
  text-decoration: none; white-space: nowrap;
  transition: background .2s, color .2s;
}
.sidebar-link .ic { flex-shrink: 0; font-size: 16px; color: color-mix(in srgb, var(--navy-on) 42%, transparent); transition: color .2s; }
.sidebar-link:hover { background: color-mix(in srgb, var(--navy-on) 6%, transparent); color: var(--navy-on); }
.sidebar-link:hover .ic { color: color-mix(in srgb, var(--navy-on) 80%, transparent); }
.sidebar-link.active { background: var(--navy-mid); color: var(--navy-on); font-weight: 600; }
.sidebar-link.active .ic { color: var(--gold); }
/* Marca dorada pegada al borde izquierdo de la barra */
.sidebar-link.active::before {
  content: ''; position: absolute; left: -14px; top: 50%;
  transform: translateY(-50%);
  width: 3px; height: 26px; border-radius: 0 3px 3px 0;
  background: var(--gold);
}
.sb-collapsed .sidebar-link { justify-content: center; padding: 11px 0; }

/* ── Pie: empresa activa y usuario ───────────────────────────────────────── */
.sidebar-user {
  display: flex; flex-direction: column; gap: 10px;
  padding-top: 16px;
  border-top: 1px solid color-mix(in srgb, var(--navy-on) 9%, transparent);
}
.sb-user-link { display: flex; align-items: center; gap: 11px; padding: 2px 6px; min-width: 0; }
.sb-avatar {
  flex: 0 0 auto; width: 32px; height: 32px;
  display: grid; place-items: center; border-radius: 9px;
  background: var(--navy-mid); color: var(--navy-on);
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
}
.sb-user-text { min-width: 0; display: block; }
.sb-org, .sb-name { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sb-org  { color: var(--gold); font-size: 12px; font-weight: 700; }
.sb-name { color: color-mix(in srgb, var(--navy-on) 72%, transparent); font-size: 11px; }
/* Rol: hereda de .lbl el 10px/700 en mayúsculas, sólo cambia el color por el
   fondo navy. En dorado cuando es administrador de plataforma, que es un
   permiso sobre todas las empresas y no sobre la activa. */
.sb-role { padding: 0 6px; color: color-mix(in srgb, var(--navy-on) 55%, transparent); }   /* era 40 %: 3,4:1 */
.sb-role.is-admin { color: var(--gold); }

/* Control discreto sobre el navy de la barra (hoy, el respaldo del selector de
   empresa). No es .btn-ghost: aquél es la terciaria sobre crema (§7.6) y acá
   haría falta el aspecto contrario, translúcido y con texto claro. */
.btn-sb {
  background: color-mix(in srgb, var(--navy-on) 7%, transparent);
  border: 1px solid color-mix(in srgb, var(--navy-on) 16%, transparent);
  color: color-mix(in srgb, var(--navy-on) 75%, transparent);
  border-radius: var(--r); padding: 7px 10px;
  font-size: 11px; font-weight: 700;
  cursor: pointer; transition: background .2s, color .2s;
}
.btn-sb:hover { background: color-mix(in srgb, var(--navy-on) 16%, transparent); color: var(--navy-on); }
/* Volver a la pantalla de elección de empresa: un enlace con el aspecto de ese
   mismo control, de ancho completo. */
.sb-back {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  text-decoration: none;
}
.sb-collapsed .sb-back { padding: 7px 0; }

.btn-logout {
  width: 100%;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 12px;
  background: transparent;
  border: 1.5px solid var(--gold); border-radius: 9px;
  color: var(--gold);
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
  cursor: pointer; white-space: nowrap;
  transition: background .2s, color .2s;
}
.btn-logout:hover { background: var(--gold); color: var(--gold-on); }
.sb-collapsed .btn-logout { padding: 10px 0; }

/* ── Barra superior y velo (sólo ≤800px) ─────────────────────────────────── */
.topbar-mobile { display: none; }
/* El velo se dibuja sólo en móvil y sólo con la barra abierta: al ensanchar la
   ventana con la barra abierta se apaga solo, sin quedarse tapando la página.
   El atributo hidden lo pone y lo quita el script; acá se lee, no se duplica. */
.sidebar-backdrop { display: none; position: fixed; inset: 0; z-index: 55; background: rgba(25, 47, 80, .45); }
/* Fundido del filtro en filtros.js al reemplazar el bloque de resultados.
   Sólo cuando el esqueleto llegó a pintarse (filtros.js pone la clase en ese
   caso y no en otro), y en 200 ms: es el tiempo del estándar para lo que
   reemplaza a una silueta. Solo opacidad: la silueta ya ocupaba el sitio. */
@keyframes bloque-nuevo { from { opacity: 0; } to { opacity: 1; } }
.js-aparece { animation: bloque-nuevo 200ms var(--ux-ease, ease-out); }

/* ============================================================================
   5. MÓDULOS: cabecera navy + cuerpo blanco  (§7.6)
   ========================================================================== */
.module-head {
  background: var(--navy);
  border-radius: 8px 8px 0 0;
  padding: 18px 22px;
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
}
.module-title { font-size: 18px; font-weight: 800; color: var(--navy-on); }
.module-title span { color: var(--gold); }
.module-sub { font-size: 12px; color: color-mix(in srgb, var(--navy-on) 55%, transparent); margin-top: 3px; }   /* era 38 %: 3,3:1, bajo AA */
.module-cnt { font-size: 21px; font-weight: 800; color: var(--gold); line-height: 1; text-align: right; }
.module-cnt-sub { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: color-mix(in srgb, var(--navy-on) 55%, transparent); text-align: right; margin-top: 3px; }
.module-head .hdr-actions { display: flex; align-items: center; gap: 8px; }

.module-body {
  background: var(--white);
  border-radius: 0 0 8px 8px;
  box-shadow: 0 4px 20px rgba(25, 47, 80, .09);
  padding: 20px 22px;
}
.module-body.flush { padding: 0; }

/* Tarjeta blanca suelta (paneles del resumen ejecutivo). */
.card {
  background: var(--white);
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(25, 47, 80, .09);
  display: flex; flex-direction: column;
  min-width: 0;
}
.card-head { padding: 14px 18px 11px; border-bottom: 1px solid var(--gray); display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.card-title { letter-spacing: 1.5px; }
.card-sub { font-size: 11px; color: var(--sub); margin-top: 4px; font-weight: 400; text-transform: none; letter-spacing: 0; }
.card-body { padding: 16px 18px; flex: 1; }
.card-body.flush { padding: 0; }

/* Banner de empresa: tarjeta blanca con acento dorado a la izquierda (§7.5). */
.cl-banner {
  background: var(--white);
  border-left: 4px solid var(--gold);
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(25, 47, 80, .08);
  padding: 14px 18px;
  display: flex; align-items: center; gap: 14px;
}
.cl-banner-name { font-size: 15px; font-weight: 800; color: var(--navy); }
.cl-banner-det { font-size: 11px; color: var(--sub); margin-top: 2px; }
.cl-banner-logo { height: 38px; max-width: 130px; object-fit: contain; }

/* ── Listas de fichas: empresas y personas ────────────────────────────────
   Alternativa a la tabla para cuando cada fila es una entidad en la que se
   entra, no un dato que se compara con el de al lado. Es el patrón de la
   pantalla de clientes de assessment_normativo: nombre grande, una línea de
   detalle debajo y las acciones a la derecha. Van dentro de un
   `.module-body.flush`, que ya les pone el marco navy + blanco. */
.cl-row {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  width: 100%;
  padding: 12px 20px;
  border: 0; border-bottom: 1px solid var(--gray);
  background: none;
  font: inherit; color: inherit; text-align: left;
  cursor: pointer;
  transition: background .15s;
}
/* Los estilos de arriba dejan la clase servible también como <button>, que es
   lo que es cada fila en la pantalla de elección de empresa (§8.1): ahí pulsar
   la fila cambia la empresa activa, y eso es un envío de formulario, no un
   enlace. En el listado del administrador la fila es un <div> y quien se pulsa
   es el enlace del nombre. */
.cl-row:last-child { border-bottom: none; }
.cl-row:hover { background: var(--cream); }
/* La empresa en la que ya se está trabajando, en la pantalla de elección. El
   acento va por dentro (box-shadow) y no como borde, que correría el contenido
   de esa fila tres píxeles respecto de las demás. */
.cl-row.is-current { box-shadow: inset 3px 0 0 var(--gold); }
.cl-row-text { display: block; flex: 1; min-width: 0; }
.cl-row-name { display: block; font-size: 13px; font-weight: 600; color: var(--navy); text-decoration: none; }
.cl-row-name:hover { color: var(--gold); }
/* El nombre es el enlace, pero lo que se pulsa es la fila entera: este ::after
   la cubre. Así hay UN solo enlace por fila —no dos al mismo sitio, que es lo
   que oiría quien navegue por lector de pantalla— y el ratón no tiene que
   apuntar justo al texto. */
.cl-row-name::after { content: ''; position: absolute; inset: 0; }
.cl-row-det { display: block; font-size: 11px; color: var(--sub); margin-top: 2px; font-weight: 400; }
.cl-row-det .crit { font-weight: 700; }
/* Los controles de la fila van por encima de esa capa, o el enlace se los
   tragaría. */
.cl-row-acts { position: relative; display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.cl-row-sel { flex-shrink: 0; display: flex; align-items: center; color: var(--navy); font-size: 15px; }

.user-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--gray);
  transition: background .15s;
}
.user-row:last-child { border-bottom: none; }
.user-row:hover { background: var(--cream); }
.user-row-name { font-size: 13px; font-weight: 600; color: var(--navy); }
.user-row-name small { font-size: 11px; font-weight: 400; color: var(--sub); }
.user-row-meta { font-size: 11px; color: var(--sub); margin-top: 2px; }
.user-row-acts { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }

/* ============================================================================
   6. BOTONES  (§7.8)
   ========================================================================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  font-size: 12px; font-weight: 700; letter-spacing: .5px;
  padding: 10px 18px;
  border-radius: var(--r); border: none;
  cursor: pointer; text-decoration: none;
  transition: background-color var(--ux-dur) var(--ux-ease), color var(--ux-dur) var(--ux-ease),
              border-color var(--ux-dur) var(--ux-ease), box-shadow var(--ux-dur) var(--ux-ease),
              opacity var(--ux-dur) var(--ux-ease);
}
/* Foco de teclado visible: el outline por defecto casi no se ve sobre navy. */
.btn:focus-visible, .btn-link:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.sidebar-link:focus-visible, .btn-sb:focus-visible, .btn-logout:focus-visible,
.sidebar-toggle:focus-visible, .btn-hamburger:focus-visible {
  outline: 2px solid var(--gold); outline-offset: -2px;
}
.btn:disabled { opacity: .55; cursor: not-allowed; }

.btn-primary { background: var(--navy); color: var(--navy-on); }
.btn-primary:hover:not(:disabled) { background: var(--navy-mid); }

.btn-next { background: var(--navy); color: var(--navy-on); box-shadow: 0 4px 12px rgba(25, 47, 80, .25); }
.btn-next:hover:not(:disabled) { background: var(--navy-mid); }

.btn-prev { background: #fff; border: 1.5px solid var(--gray); color: var(--navy); }
.btn-prev:hover:not(:disabled) { border-color: var(--navy); }

.btn-gold { background: var(--gold); color: var(--gold-on); box-shadow: 0 4px 12px rgba(255, 131, 0, .28); }
.btn-gold:hover:not(:disabled) { background: var(--gold-light); }

.btn-ghost { background: transparent; border: 1.5px solid var(--gray); color: var(--sub); }
.btn-ghost:hover:not(:disabled) { border-color: var(--navy); color: var(--navy); }

.btn-danger { background: rgba(231, 76, 60, .1); color: var(--err); border: 1px solid rgba(231, 76, 60, .3); }
/* Los tres de arriba no respondían al ratón deshabilitados ni ocupados; estos
   sí lo hacían. Ahora ninguno. */
.btn-danger:hover:not(:disabled) { background: var(--err-fill); color: #fff; border-color: var(--err-fill); }

.btn-sm { padding: 6px 12px; font-size: 11px; }

.btn-link {
  background: none; border: 0; padding: 0;
  color: var(--navy); font-size: 11px; font-weight: 700;
  letter-spacing: .5px; cursor: pointer; text-decoration: none;
  transition: color var(--ux-dur-fast, .15s) ease;
}
.btn-link:hover { color: var(--gold); }

/* Enlaces de texto que cambian de color al pasar: el cambio se funde en
   150 ms en vez de saltar, como ya hacían los botones y la barra lateral. */
.crumbs a,
.cl-row-name,
.find-title,
.hbar-label,
.data-table a.cell-link,
.data-table thead th a {
  transition: color var(--ux-dur-fast, .15s) ease, text-decoration-color var(--ux-dur-fast, .15s) ease;
}

/* ============================================================================
   7. FORMULARIOS  (§7.9)
   ========================================================================== */
.fl { margin-bottom: 16px; min-width: 0; }
.fl label, .form-label { display: block; margin-bottom: 6px; }

.fl input[type="text"], .fl input[type="email"], .fl input[type="password"],
.fl input[type="date"], .fl input[type="search"], .fl input[type="file"],
.fl select, .fl textarea,
.form-input, .form-select, .form-textarea {
  width: 100%;
  padding: 11px 13px;
  border: 1.5px solid var(--field-border);
  border-radius: var(--r);
  font-size: 14px;
  background: var(--field-bg);
  color: var(--text);
  transition: border-color .2s;
}
.fl input:focus, .fl select:focus, .fl textarea:focus,
.form-input:focus, .form-select:focus, .form-textarea:focus {
  border-color: var(--field-focus);
  background-color: #fff;
  outline: none;
}

/* Los <select> se dibujan enteros acá y no se dejan al navegador: sin esto
   macOS pinta su propio control —más bajo que un input, con su doble flecha y
   su tipografía— y en una fila de filtros se ve un cajón distinto por campo.
   La flecha es la de Feather en un data: URI (la CSP admite `img-src data:`).
   Con appearance:none la caja pasa a medir padding + línea, igual que un input,
   así que alinean solos sin fijarles alto. */
.fl select, .form-select {
  appearance: none; -webkit-appearance: none;
  padding-right: 34px;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235a6070' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 11px;
}
/* Deshabilitado se ve deshabilitado: atenuado y con el cursor de «no».
   Antes sólo el <select> lo marcaba, y con el cursor normal. */
.fl :is(input, select, textarea):disabled,
.form-input:disabled, .form-select:disabled, .form-textarea:disabled { cursor: not-allowed; opacity: .6; }

/* Al pasar por encima el borde se oscurece un paso, para que el campo diga
   que se puede escribir en él. No con foco (ahí manda el dorado) ni
   deshabilitado. */
:is(.fl input[type="text"], .fl input[type="email"], .fl input[type="password"],
    .fl input[type="date"], .fl input[type="search"], .fl input[type="file"],
    .fl select, .fl textarea,
    .form-input, .form-select, .form-textarea):hover:not(:focus):not(:disabled):not(:user-invalid) {
  border-color: var(--gray-2);
}
/* Error del navegador (obligatorio vacío, correo mal escrito) en rojo, pero
   sólo después de que la persona tocó el campo: :user-invalid no se activa
   al cargar la página, a diferencia de :invalid. */
:is(.fl input, .fl select, .fl textarea,
    .form-input, .form-select, .form-textarea):user-invalid {
  border-color: var(--err);
}
.form-textarea { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; resize: vertical; min-height: 180px; }

/* Campo de contraseña con ojo. El botón va dentro del campo, así que el input
   se reserva el hueco a la derecha y no escribe nunca debajo del icono.
   Nace con [hidden] y lo muestra app.js: sin JavaScript no habría nada que
   conmutar, y un mando muerto es peor que no tenerlo. El alto se lo da el
   input, así que el botón se centra sobre él y no sobre la fila entera. */
.pass-wrap { position: relative; }
/* Los dos tipos, y con el atributo puesto: el kit de campos de arriba selecciona
   por [type=…] y ganaría a un `.pass-wrap input` a secas. `text` hace falta
   porque es lo que pasa a ser el campo cuando se destapa. */
.pass-wrap input[type="password"],
.pass-wrap input[type="text"] { padding-right: 40px; }
.pass-toggle {
  position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  width: 30px; height: 30px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: 0; border-radius: 4px;
  color: var(--sub); font-size: 16px; line-height: 1;
  cursor: pointer; transition: color .2s;
}
.pass-toggle[hidden] { display: none; }
.pass-toggle:hover { color: var(--navy); }
/* En pantalla táctil el ojo mide 30 px y el dedo pide 44: se agranda el área
   que recibe el toque, no el icono. Sólo con puntero grueso, para que con
   ratón el borde derecho del campo siga siendo campo. */
@media (pointer: coarse) {
  .pass-toggle::before { content: ''; position: absolute; inset: -7px; }
}
.form-help  { font-size: 11px; color: var(--sub); margin-top: 5px; }
.form-error { font-size: 11px; color: var(--err); margin-top: 5px; font-weight: 600; }

.fl-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 0 16px; }
.fl-row  { display: flex; gap: 14px; align-items: flex-end; flex-wrap: wrap; }
.fl-row .fl { position: relative; margin-bottom: 0; }
/* La fila alinea por abajo, que es lo que deja el botón a la altura de los
   campos aunque no tenga etiqueta encima. Pero un campo con ayuda o con error
   debajo mide más, y alineando por abajo subiría respecto de los demás: las
   etiquetas quedarían a distinta altura y los cuadros también (es lo que pasaba
   en «Nueva empresa»). Así que ese texto sale del flujo y la fila le reserva el
   hueco por debajo. */
.fl-row .form-help, .fl-row .form-error { position: absolute; top: 100%; left: 0; right: 0; margin-top: 5px; }
.fl-row:has(.form-help, .form-error) { padding-bottom: 22px; }
.fl-actions { display: flex; gap: 10px; align-items: center; }

/* Campo de color: muestra de color + hex escrito a mano, los dos sincronizados. */
.color-field { display: flex; align-items: center; gap: 9px; }
.color-field input[type="color"] {
  width: 46px; height: 40px; padding: 3px;
  border: 1.5px solid var(--field-border); border-radius: var(--r);
  background: var(--field-bg); cursor: pointer;
}
.color-field input[type="text"] { max-width: 130px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; text-transform: uppercase; }

/* Letra sobre un color de marca: dos opciones, cada una con su muestra —el
   color de fondo ya elegido con la letra encima— para decidir a ojo. */
.on-pick { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 10px; }
.on-pick .lbl { margin: 0; }
/* `.on-pick` delante para ganarle al `.fl label { display:block }` del kit. */
.on-pick .on-opt { display: inline-flex; align-items: center; gap: 7px; margin: 0; font-size: 12px; cursor: pointer; }
.on-opt input { margin: 0; accent-color: var(--gold); }
.on-swatch {
  display: inline-grid; place-items: center;
  width: 30px; height: 22px; border-radius: 4px;
  border: 1px solid var(--gray);
  font-size: 11px; font-weight: 800;
}

/* ============================================================================
   8. AVISOS  (§7.10)
   ========================================================================== */
/* Un solo aspecto para todos los avisos de error: navy con borde rojo. */
.err-box {
  display: flex; gap: 9px; align-items: flex-start;
  background: var(--navy);
  border: 1.5px solid var(--err-line);
  border-radius: var(--r);
  padding: 11px 14px;
  color: color-mix(in srgb, var(--navy-on) 85%, transparent);
  font-size: 12px;
}
.err-box .ic { color: var(--err-line); font-size: 16px; margin-top: 1px; }
.err-box ul { margin: 6px 0 0; padding-left: 18px; }
.err-box strong { color: var(--navy-on); }

.empty-state { padding: 40px 20px; text-align: center; color: var(--sub); font-size: 13px; }
.empty-state strong { display: block; color: var(--navy); font-size: 14px; font-weight: 700; margin-bottom: 6px; }

/* ============================================================================
   9. TABLAS  (§7.11)
   ========================================================================== */
/* El contenedor desplazable de toda tabla (§7.10). El suelo que lo hace
   funcionar de verdad está en el bloque responsive del final: con `width:100%`
   y sin `min-width`, la tabla siempre cabe en su contenedor y este scroll no
   llega a activarse nunca. */
.table-wrap { overflow-x: auto; }
.data-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.data-table thead th {
  text-align: left;
  letter-spacing: 1.5px;
  padding: 11px 12px;
  border-bottom: 2px solid var(--gray);
  white-space: nowrap;
}
.data-table thead th a { text-decoration: none; color: inherit; }
.data-table thead th a:hover { color: var(--navy); }
.data-table tbody td { padding: 10px 12px; border-bottom: 1px solid var(--gray); vertical-align: middle; }
.data-table tbody tr:last-child td { border-bottom: 0; }
.data-table tbody tr:hover { background: var(--cream); }
.data-table tbody tr.dimmed { opacity: .55; }
.data-table .num { text-align: right; }
.data-table .center { text-align: center; }
.data-table a.cell-link { color: var(--navy); text-decoration: none; font-weight: 600; }
.data-table a.cell-link:hover { color: var(--gold); }

/* Paginación bajo una tabla. */
.pagination {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px;
  border-top: 1px solid var(--gray);
  font-size: 11px; font-weight: 600; color: var(--sub);
}
.pagination .spacer { margin-left: auto; display: flex; gap: 7px; align-items: center; }
.pagination a, .pagination span.page-link {
  padding: 5px 11px;
  border: 1.5px solid var(--gray); border-radius: var(--r);
  color: var(--sub); text-decoration: none;
}
.pagination a { transition: border-color var(--ux-dur-fast, .15s) ease, color var(--ux-dur-fast, .15s) ease; }
.pagination a:hover { border-color: var(--navy); color: var(--navy); }
.pagination .disabled { opacity: .45; cursor: not-allowed; }

/* ============================================================================
   10. TAGS Y BADGES  (§4, §7.7, §7.11)
   ========================================================================== */
.tag {
  display: inline-block;
  font-size: 10px; font-weight: 600; letter-spacing: 1px; text-transform: uppercase;
  border-radius: 2px; padding: 3px 7px;
  background: rgba(25, 47, 80, .07); color: #264168;
}
.tag-gold { background: rgba(255, 131, 0, .14); color: #7a5c10; }
.tag-mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0; text-transform: none; }

/* Insignia de estado: fondo suave + texto del tono saturado (patrón de §4). */
.sev-badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  border-radius: 20px; padding: 3px 10px;
  border: 1px solid transparent;
  white-space: nowrap;
}

/* Insignia de RIESGO: relleno lleno, como las celdas del informe. Es la única
   que se aparta del patrón de fondo suave, y a propósito: el nivel de riesgo es
   el dato que se busca de un vistazo, y así se lee igual en pantalla y en el
   Word entregado. */
.risk-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 62px;
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  border-radius: 3px; padding: 3px 9px;
  white-space: nowrap;
}
.sev-dot { width: 7px; height: 7px; border-radius: 50%; display: inline-block; flex-shrink: 0; }
.sev-dot-wrap { display: inline-flex; align-items: center; gap: 6px; }

/* Referencia CVE / plugin. */
.ref { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; color: var(--navy); font-weight: 600; }
a:hover > .ref { color: var(--gold); }

/* ============================================================================
   11. KPI Y GRÁFICOS  (§12 adaptado a SVG servidos por PHP)
   ========================================================================== */
.kpi-grid { display: grid; grid-template-columns: 1.3fr repeat(4, 1fr); gap: 12px; }
.kpi-card {
  background: var(--white); border-radius: 8px;
  box-shadow: 0 2px 12px rgba(25, 47, 80, .09);
  padding: 15px 18px;
  border-left: 3px solid transparent;
  transition: box-shadow var(--ux-dur) var(--ux-ease), border-left-color var(--ux-dur) var(--ux-ease);
}
.kpi-card:hover { box-shadow: 0 4px 16px rgba(25, 47, 80, .13); }
.kpi-card.accent { border-left-color: var(--gold); }
.kpi-label { margin-bottom: 9px; }
.kpi-value { font-size: 30px; font-weight: 800; color: var(--navy); line-height: 1; }
.kpi-card.big .kpi-value { font-size: 44px; }
.kpi-foot { font-size: 11px; color: var(--sub); margin-top: 9px; padding-top: 8px; border-top: 1px solid var(--gray); }

.dash-grid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 14px; }
.span-4 { grid-column: span 4; } .span-6 { grid-column: span 6; }
.span-8 { grid-column: span 8; } .span-12 { grid-column: span 12; }

/* Anillo de distribución */
.donut-row { display: flex; gap: 18px; align-items: center; }
.donut-svg { display: block; flex-shrink: 0; }
.donut-hole { text-align: center; }
.donut-hole .val { font-size: 24px; font-weight: 800; color: var(--navy); line-height: 1; }
.donut-hole .lbl { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--sub); margin-top: 3px; }
.donut-wrap { position: relative; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.donut-wrap .donut-hole { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }

.donut-legend { flex: 1; display: flex; flex-direction: column; gap: 9px; min-width: 0; }
.legend-row { display: grid; grid-template-columns: 9px 58px 1fr 34px 42px; align-items: center; gap: 8px; font-size: 12px; }
.legend-mark { width: 9px; height: 9px; border-radius: 2px; }
.legend-label { color: var(--sub); font-weight: 600; }
.legend-bar { height: 6px; background: var(--cream-2); border-radius: 3px; overflow: hidden; }
.legend-fill { height: 100%; border-radius: 3px; }
.legend-val { font-weight: 700; text-align: right; color: var(--navy); }
.legend-pct { color: var(--sub); font-size: 10px; text-align: right; }

/* Barras horizontales */
.hbar-list { display: flex; flex-direction: column; gap: 9px; }
.hbar-row { display: grid; grid-template-columns: 120px 1fr 48px; align-items: center; gap: 12px; }
.hbar-label { font-size: 12px; font-weight: 600; color: var(--navy); text-decoration: none; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
a.hbar-label:hover { color: var(--gold); }
.hbar-track { position: relative; background: var(--cream-2); border-radius: 3px; overflow: hidden; }
/* El extremo del dato va redondeado y el arranque a ras del origen: una
   barra redondeada por los dos lados se lee como una pastilla flotante y
   pierde el anclaje con el eje. min-width deja visible un valor de 1 que de
   otro modo se pintaria como una linea de medio pixel. */
.hbar-fill { height: 100%; border-radius: 0 4px 4px 0; min-width: 3px; }
.hbar-value { font-size: 12px; font-weight: 700; text-align: right; color: var(--navy); font-variant-numeric: tabular-nums; }

/* Tendencia */
/* height:auto y no una altura fija: junto con quitar preserveAspectRatio
   ="none" del SVG, es lo que hace que el dibujo escale igual en los dos ejes
   y el texto de los ejes no salga condensado. */
.trend-svg { width: 100%; height: auto; display: block; }
.trend-legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11px; font-weight: 600; color: var(--sub); margin-top: 10px; }
.trend-legend .key { display: inline-flex; align-items: center; gap: 6px; }
.trend-legend i { width: 12px; height: 2px; border-radius: 2px; display: inline-block; }

/* MTTR y antigüedad */
.mttr-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.mttr-card { padding: 13px; background: var(--cream); border-radius: var(--r); text-align: center; }
.mttr-value { font-size: 24px; font-weight: 800; line-height: 1; }
.mttr-unit { font-size: 13px; font-weight: 600; margin-left: 2px; }
.mttr-foot { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--sub); margin-top: 7px; }

.aging-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.aging-cell { padding: 13px; background: rgba(25, 47, 80, .03); border-left: 3px solid var(--navy-light); border-radius: var(--r); text-align: center; }
.aging-value { font-size: 21px; font-weight: 800; color: var(--navy); line-height: 1; }
.aging-label { margin-top: 6px; }

/* Un tramo vacío entre otros con carga: sigue estando —los tramos son
   excluyentes y hay que poder leer el reparto— pero deja de pesar como si
   tuviera un dato. */
.aging-cell.is-zero { background: transparent; border-left-color: var(--gray); }
.aging-cell.is-zero .aging-value { color: var(--sub); opacity: .45; font-weight: 700; }

/* Ninguna arrastrada: es el resultado bueno y se dice, en vez de pintar tres
   ceros que ocupan el sitio de un problema inexistente. */
.aging-clear {
  display: flex; align-items: center; gap: 8px;
  padding: 13px;
  background: var(--status-resolved-soft);
  /* Borde fino del mismo verde en vez de la barra de 3 px a la izquierda:
     es un aviso, no una celda de tramo como las de al lado. */
  border: 1px solid color-mix(in srgb, var(--status-resolved) 35%, transparent);
  border-radius: var(--r);
  font-size: 12px; font-weight: 600; color: var(--ok);
}
.aging-clear .ic { color: var(--status-resolved); }

/* Tarjetas de resumen (diferencial, resultado de la ingesta) */
.sum-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; }
.sum-card { padding: 15px; text-align: center; border-radius: var(--r); border: 1px solid transparent; }
.sum-card .val { font-size: 24px; font-weight: 800; line-height: 1; }
.sum-card .lbl { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; margin-top: 5px; }

/* ============================================================================
   12. BARRA DE FILTROS Y PESTAÑAS  (§7.12)
   ========================================================================== */
.filters {
  display: flex; align-items: flex-end; gap: 16px; flex-wrap: wrap;
  padding: 16px 20px;
  background: var(--white); border-radius: 8px;
  box-shadow: 0 2px 12px rgba(25, 47, 80, .09);
}
/* Un ancho mínimo por campo, para que ninguno se estruje al envolver la fila.
   Es el .campo del panel de filtros de Control de Horas. */
.filters .fl { margin-bottom: 0; min-width: 165px; }
.filters .fl label { margin-bottom: 6px; }
.filters .fl input, .filters .fl select { padding: 9px 12px; font-size: 13px; }
.filters .fl select { padding-right: 32px; }
/* Todo lo que se apoya en la línea de abajo mide lo mismo. Sin esto un botón
   —que es más bajo que un campo— deja un escalón en ese borde, que es justo
   donde la barra alinea. */
.filters .fl input, .filters .fl select, .filters .fl-actions .btn { height: 40px; }
/* El buscador se queda con el espacio que sobra. */
.filters .search { flex: 1; min-width: 220px; }
/* Los botones miden lo que midan: no les hace falta el ancho mínimo de un
   campo con dato dentro, que dejaría un hueco a su derecha. */
.filters .fl-actions { min-width: 0; }
.filters .result-count { margin-left: auto; font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--sub); }

.tabs { display: flex; gap: 0; border-bottom: 2px solid var(--gray); }
.tab {
  padding: 9px 16px;
  font-size: 11px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  color: var(--sub); text-decoration: none;
  border-bottom: 2px solid transparent; margin-bottom: -2px;
  transition: color var(--ux-dur-fast, .15s) ease, border-color var(--ux-dur-fast, .15s) ease;
}
.tab:hover { color: var(--navy); }
.tab.active { color: var(--navy); border-bottom-color: var(--gold); }

/* Selector de modo (subida de scans): dos pestañas en cápsula. */
.mode-tabs { display: inline-flex; border: 1.5px solid var(--gray); border-radius: var(--r); overflow: hidden; }
.mode-tabs a {
  padding: 7px 15px;
  font-size: 11px; font-weight: 700; letter-spacing: .5px;
  color: var(--sub); text-decoration: none; background: #fff;
  transition: color var(--ux-dur-fast, .15s) ease, background-color var(--ux-dur-fast, .15s) ease;
}
/* La opción no elegida no contestaba al ratón: era el único par de pestañas
   de la app sin estado de paso. */
.mode-tabs a:not(.active):hover { color: var(--navy); background: var(--cream); }
.mode-tabs a + a { border-left: 1.5px solid var(--gray); }
.mode-tabs a.active { background: var(--gold); color: var(--gold-on); }

/* ============================================================================
   13. LISTA DE SCANS Y DIFERENCIAL
   ========================================================================== */
.scans-layout { display: grid; grid-template-columns: 280px 1fr; gap: 18px; align-items: start; }
.scan-list { display: flex; flex-direction: column; }
.scan-item {
  display: block; padding: 12px 16px;
  border-bottom: 1px solid var(--gray);
  border-left: 3px solid transparent;
  text-decoration: none;
  transition: background-color var(--ux-dur) var(--ux-ease), border-left-color var(--ux-dur) var(--ux-ease),
              color var(--ux-dur) var(--ux-ease);
}
.scan-item:last-child { border-bottom: 0; }
.scan-item:hover { background: var(--cream); }
.scan-item.active { background: rgba(255, 131, 0, .07); border-left-color: var(--gold); }
.scan-date { font-size: 13px; font-weight: 700; color: var(--navy); font-variant-numeric: tabular-nums; }
.scan-tools { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 5px; }

.diff-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.diff-col { background: var(--cream); border-radius: var(--r); padding: 13px; }
.diff-col-head {
  display: flex; align-items: baseline; gap: 9px; margin-bottom: 11px;
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
}
.diff-col-count { font-size: 21px; font-weight: 800; letter-spacing: -.5px; }
.diff-items { display: flex; flex-direction: column; gap: 7px; }
.diff-item {
  display: flex; align-items: center; gap: 10px;
  padding: 9px 10px;
  background: var(--white); border: 1px solid var(--gray); border-radius: var(--r);
  font-size: 12px;
}

/* ============================================================================
   14. DETALLE (vulnerabilidad / asset)
   ========================================================================== */
.detail-head { display: flex; align-items: flex-start; gap: 22px; }
.detail-main { flex: 1; min-width: 0; }
.detail-title { font-size: 18px; font-weight: 800; color: var(--navy); margin: 6px 0 9px; }
.detail-desc { font-size: 13px; color: var(--sub); line-height: 1.6; margin-bottom: 11px; }
.detail-meta { display: flex; gap: 9px; align-items: center; flex-wrap: wrap; }
.score-big { text-align: right; flex-shrink: 0; }
.score-value { font-size: 44px; font-weight: 800; line-height: 1; }
.score-label { margin-top: 5px; }

/* Bloque de metadatos con acento lateral navy (§7.7). */
.meta-block { background: rgba(25, 47, 80, .03); border-left: 3px solid var(--navy-light); border-radius: 0 var(--r) var(--r) 0; padding: 12px 15px; margin-top: 14px; }
.meta-row { display: flex; gap: 10px; font-size: 12px; margin-bottom: 6px; }
.meta-row:last-child { margin-bottom: 0; }
.meta-lbl { min-width: 120px; font-size: 11px; font-weight: 700; color: var(--navy-light); }

/* ============================================================================
   15. LOGIN  (§8)
   ========================================================================== */
.login-wrap {
  min-height: 100vh;
  display: flex; align-items: center; justify-content: center;
  /* Única excepción a "nada de hex literales" (§2): en el login todavía no hay
     sesión y por tanto no hay empresa cuya marca aplicar, así que el degradado
     es siempre el de la plataforma. Va con los valores literales de §8 de la
     guía para que se lea igual que en el resto de la familia. */
  background: linear-gradient(135deg, #192F50 0%, #264168 60%, #304F7B 100%);
  padding: 24px;
}
.login-card {
  background: #fff; border-radius: 16px;
  padding: 34px 30px; width: 100%; max-width: 360px;
  box-shadow: 0 22px 60px rgba(0, 0, 0, .28);
  border: 1px solid rgba(25, 47, 80, .08);
}
.l-logo { display: block; margin: 0 auto 16px; max-width: 170px; max-height: 60px; object-fit: contain; }
.l-badge {
  display: inline-block; background: var(--gold); color: var(--gold-on);
  font-size: 10px; font-weight: 800; letter-spacing: 2px; text-transform: uppercase;
  padding: 5px 10px; border-radius: 2px;
}
.l-title { font-size: 21px; font-weight: 800; color: var(--navy); text-align: center; margin: 12px 0 4px; }
.l-sub { font-size: 12px; color: var(--sub); text-align: center; margin-bottom: 24px; }
.l-head { text-align: center; }
.login-card .btn-primary { width: 100%; }
.l-divider { display: flex; align-items: center; gap: 10px; font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--sub); margin: 18px 0; }
.l-divider::before, .l-divider::after { content: ""; flex: 1; height: 1px; background: var(--gray); }
.oauth-list { display: flex; flex-direction: column; gap: 9px; }
.oauth-list .btn { width: 100%; }

/* ── Pantalla de elección de empresa (§8.1) ───────────────────────────────
   Va sin barra lateral —todavía no hay empresa elegida—, así que lleva su
   propia cabecera navy, mínima, y el contenido centrado debajo. */
.pick-wrap { min-height: 100vh; display: flex; flex-direction: column; background: var(--cream); }
.pick-bar { background: var(--navy); box-shadow: 0 2px 20px rgba(0, 0, 0, .4); }
.pick-bar-inner {
  max-width: 900px; margin: 0 auto; padding: 12px 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 16px;
}
.pick-brand { display: flex; align-items: center; gap: 11px; min-width: 0; }
.pick-bar-right { display: flex; align-items: center; gap: 12px; flex-shrink: 0; }
.pick-user { font-size: 11px; color: color-mix(in srgb, var(--navy-on) 65%, transparent); }
.pick-page {
  width: 100%; max-width: 900px; margin: 28px auto; padding: 0 20px;
  display: flex; flex-direction: column; gap: 18px;
}

/* ============================================================================
   16. ESQUELETO DE CARGA  (§13 — bloque común a las cinco apps de la familia)
   ==========================================================================
   Se copia tal cual para que no diverja entre repos. Lo usa el filtrado sin
   recarga de public/js/filtros.js, que calca el esqueleto de lo que había en
   pantalla: no se escriben esqueletos a mano por vista. */
.js-cargando { position: relative; }
.sk { pointer-events: none; }
.sk-caja { background: var(--white); border: 1px solid var(--gray); border-radius: var(--r); padding: 18px 20px; overflow: hidden; }
.sk-linea { height: 11px; border-radius: 4px; margin-bottom: 12px; background: linear-gradient(90deg, var(--cream-2) 25%, var(--gray) 37%, var(--cream-2) 63%); background-size: 400% 100%; animation: sk-brillo 1.4s ease-in-out infinite; }
.sk-linea:last-child { margin-bottom: 0; }
.sk-linea.es-titulo { height: 15px; margin-bottom: 18px; background: linear-gradient(90deg, var(--gray) 25%, var(--gray-2) 37%, var(--gray) 63%); background-size: 400% 100%; }
@keyframes sk-brillo { from { background-position: 100% 50%; } to { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) {
  .sk-linea, .sk-linea.es-titulo { background: var(--gray); animation: none; }
}
/* Filtrando al vuelo, el botón de enviar sobra. Se esconde desde el CSS y no
   desde el JS para que no llegue a verse; la marca la pone el <head> de
   layouts/app.php sólo si el navegador puede con el motor. */
.filtro-auto [data-filtro] [type="submit"] { display: none; }
.sk-estado { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }

/* ============================================================================
   17. CONSOLA SQL  (§7.14 — sólo administrador de plataforma)
   ========================================================================== */
/* El aviso de qué se permite. Va en navy y no en rojo a propósito: no es una
   alerta —no ha pasado nada malo— sino la regla de la casa, y un recuadro rojo
   permanente en una pantalla que se usa a diario deja de leerse a la semana.

   `.note-box` es el mismo bloque con nombre genérico: nació acá, pero el aviso
   de <noscript> de los layouts necesitaba exactamente esto y duplicar la regla
   con otro nombre habría sido fabricar una segunda caja informativa. Cuando la
   consola deje de ser el único sitio que la usa, `.sql-note` se retira. */
.note-box,
.sql-note {
  display: flex; gap: 9px; align-items: flex-start;
  /* Caja con borde de 1 px y fondo teñido, como el resto de los avisos de la
     suite (§13.4 del estándar). Antes llevaba además una barra navy de 3 px a
     la izquierda. */
  background: rgba(25, 47, 80, .05);
  border: 1px solid color-mix(in srgb, var(--navy) 16%, transparent);
  border-radius: var(--r);
  padding: 11px 13px;
  margin-bottom: 16px;
  font-size: 12px; line-height: 1.55; color: var(--sub);
}
.note-box .ic,
.sql-note .ic { color: var(--navy); margin-top: 1px; }
.note-box code,
.sql-note code, .sql-check code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  background: rgba(25, 47, 80, .07);
  border-radius: 2px; padding: 1px 4px; color: var(--navy);
}

/* La caja de texto es el centro de la pantalla: tabulaciones reales, sin
   corrección ortográfica y con el mismo cuerpo que la rejilla de resultados,
   para que una columna copiada de abajo calce al pegarla arriba. */
.sql-input {
  width: 100%;
  font-size: 13px; line-height: 1.6;
  tab-size: 2;
  resize: vertical;
  min-height: 150px;
}

.sql-controls {
  display: flex; align-items: center; gap: 18px; flex-wrap: wrap;
  margin-top: 4px;
}
.sql-controls .btn { margin-left: auto; }

.sql-check {
  display: inline-flex; align-items: center; gap: 8px;
  font-size: 12px; color: var(--text); cursor: pointer;
  user-select: none;
}
.sql-check input { margin: 0; accent-color: var(--gold); }

/* Rejilla de resultados: cuerpo monoespaciado porque acá se comparan valores
   carácter a carácter —un id, un hash, una fecha— y la proporcional engaña. */
.sql-grid { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.sql-grid thead th { white-space: nowrap; }
.sql-grid tbody td { white-space: nowrap; vertical-align: top; }
.sql-null { color: var(--sub); font-style: italic; opacity: .7; }

/* ============================================================================
   18. RESUMEN EJECUTIVO
   ==========================================================================
   La pantalla que abre la aplicación. No es una rejilla de tarjetas iguales:
   arriba va UNA frase con el estado del ciclo, debajo la comparación contra el
   ciclo anterior y las cuatro cifras del informe, y el detalle queda repartido
   en pestañas para que la primera pantalla quepa sin desplazar.

   El color de severidad que aparece acá es nivel 3 (viene del .docx) y se usa
   tal cual; lo que NO se repite es el rojo/ámbar/verde de feedback, que en
   esta pantalla no significa «algo salió mal» sino el nivel de riesgo.
   ========================================================================== */

/* ── Titular del ciclo ───────────────────────────────────────────────────── */
.hero {
  background: var(--white);
  border-left: 4px solid var(--gray-2);
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(25, 47, 80, .09);
  padding: 22px 26px;
  display: flex; align-items: flex-start; gap: 26px; flex-wrap: wrap;
}
/* El filo izquierdo es el semáforo del ciclo, en los mismos tres colores que
   el resto de la pantalla: nada de verde/ámbar/rojo de feedback. */
.hero.is-ok   { border-left-color: var(--status-resolved); }
.hero.is-warn { border-left-color: var(--sev-medium); }
.hero.is-bad  { border-left-color: var(--sev-critical); }

.hero-main { flex: 1 1 360px; min-width: 0; }
.hero-state { display: flex; align-items: center; gap: 11px; margin-bottom: 13px; flex-wrap: wrap; }
.hero-lamps { display: inline-flex; gap: 5px; }
.hero-lamp { width: 9px; height: 9px; border-radius: 50%; background: var(--gray-2); }
.hero.is-ok   .hero-lamp.on { background: var(--status-resolved); }
.hero.is-warn .hero-lamp.on { background: var(--sev-medium); }
.hero.is-bad  .hero-lamp.on { background: var(--sev-critical); }
.hero.is-ok   .hero-label { color: var(--ok); }
.hero.is-warn .hero-label { color: var(--sev-medium-ink); }
.hero.is-bad  .hero-label { color: var(--sev-critical-ink); }

.hero-title {
  font-size: 30px; font-weight: 800; color: var(--navy);
  line-height: 1.15; letter-spacing: -.5px;
  margin-bottom: 11px;
}
.hero-text { font-size: 13px; color: var(--sub); line-height: 1.65; max-width: 64ch; }

/* Score y críticas, a la derecha y separados por un filo: son el dato duro
   detrás de la frase, no dos tarjetas más. */
.hero-figs { display: flex; gap: 28px; flex: 0 0 auto; padding-left: 26px; border-left: 1px solid var(--gray); }
.hero-fig .val { font-size: 44px; font-weight: 800; color: var(--navy); line-height: 1; margin-top: 8px; }
.hero-fig .delta { font-size: 11px; font-weight: 700; margin-top: 7px; }
.hero-fig .delta.down { color: var(--ok); }
.hero-fig .delta.up   { color: var(--err); }
.hero-fig .delta.flat { color: var(--sub); }

/* ── Comparación contra el ciclo anterior + cifras del informe ───────────── */
.dash-top { display: grid; grid-template-columns: 1fr 350px; gap: 14px; align-items: stretch; }
/* Las dos columnas terminan a la misma altura: las cifras se estiran para
   llenar su columna y las filas de la comparación se reparten el alto de la
   tarjeta, en vez de dejar un hueco suelto al pie de la más corta. */
.fig-col { display: flex; flex-direction: column; gap: 12px; }
.fig-col .fig-card { flex: 1; }
.cmp-body { display: flex; flex-direction: column; justify-content: space-around; }
.fig-card {
  background: var(--white); border-radius: 8px;
  box-shadow: 0 2px 12px rgba(25, 47, 80, .09);
  padding: 17px 20px;
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
}
.fig-card .lbl { font-size: 13px; font-weight: 600; color: var(--text); }
.fig-card .val { font-size: 24px; font-weight: 800; color: var(--navy); line-height: 1; }

/* Una fila por severidad: barra clara el ciclo anterior, barra llena este, y
   a la derecha el paso de un número al otro. Dos barras y no una agrupada
   porque lo que se compara es la MISMA severidad en dos meses. */
.cmp-row { display: grid; grid-template-columns: 74px 1fr 106px; align-items: center; gap: 14px; margin-bottom: 15px; }
.cmp-row:last-child { margin-bottom: 0; }
.cmp-bars { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.cmp-bar { height: 7px; border-radius: 0 4px 4px 0; background: var(--cream-2); }
/* Un cero no se dibuja: una barra de 3 px de ancho se lee como «poco», no como
   «ninguno», y el número de la derecha ya lo dice. */
.cmp-bar.is-zero { background: transparent; }
.cmp-delta { display: flex; align-items: center; justify-content: flex-end; gap: 11px; font-size: 12px; color: var(--sub); }
.cmp-delta b { color: var(--navy); font-weight: 700; }
.cmp-move { min-width: 26px; text-align: right; font-weight: 700; }
.cmp-move.down { color: var(--ok); }
.cmp-move.up   { color: var(--err); }
.cmp-move.flat { color: var(--sub); font-weight: 400; }

/* ── La acción que cierra la crítica ─────────────────────────────────────── */
.one-act {
  display: flex; align-items: center; gap: 14px; flex-wrap: wrap;
  padding: 13px 16px;
  background: var(--sev-critical-soft);
  border: 1px solid var(--sev-critical-border);
  border-radius: 8px;
}
.one-act-tag {
  background: var(--sev-critical); color: var(--sev-critical-on);
  font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase;
  border-radius: 3px; padding: 4px 10px; white-space: nowrap;
}
.one-act-text { flex: 1 1 260px; min-width: 0; font-size: 13px; font-weight: 600; color: var(--navy); }
.one-act-meta { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; font-weight: 400; color: var(--sub); }

/* ── Pestañas del detalle ────────────────────────────────────────────────── */
/* Dentro de la tarjeta, la barra de pestañas se separa del contenido y del
   borde de la tarjeta por sí misma: el `.card-body` no le puede dar margen
   sin dárselo también al contenido. */
.tab-bar { margin-bottom: 18px; }
/* Cada panel es una columna con aire propio: sin esto cada pestaña tendría que
   acordarse de separar sus bloques. */
.tab-panel { display: flex; flex-direction: column; gap: 18px; }
.tab-split { display: grid; grid-template-columns: 1fr 330px; gap: 22px; align-items: start; }
.tab-half  { display: grid; grid-template-columns: repeat(2, 1fr); gap: 22px; align-items: start; }
/* Título de bloque dentro de un panel: no es una tarjeta, así que no lleva
   `.card-head`, pero sí la misma pareja título + subtítulo. */
.blk-head { margin-bottom: 13px; }
.blk-title { font-size: 13px; font-weight: 700; color: var(--navy); }
.blk-sub { font-size: 11px; color: var(--sub); margin-top: 3px; }
.blk-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 14px; flex-wrap: wrap; margin-bottom: 13px; }
/* Caja de apoyo a la derecha de un panel (distribución, score proyectado):
   crema y sin sombra, para que no compita con la tarjeta que la contiene. */
.side-box { background: var(--cream); border-radius: 8px; padding: 16px 18px; }
.side-box + .side-box { margin-top: 14px; }
.side-title { margin-bottom: 12px; }

/* ── Pestaña «Hallazgos» ─────────────────────────────────────────────────── */
.find-row { display: flex; align-items: center; gap: 12px; padding: 12px 0; border-bottom: 1px solid var(--gray); }
.find-row:last-child { border-bottom: 0; }
.find-main { flex: 1; min-width: 0; }
.find-title { font-size: 13px; font-weight: 600; color: var(--navy); text-decoration: underline; text-underline-offset: 3px; text-decoration-color: var(--gray-2); }
.find-title:hover { color: var(--gold); text-decoration-color: var(--gold); }
.find-meta { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: var(--sub); margin-top: 4px; }
.find-score { min-width: 34px; text-align: right; font-size: 13px; font-weight: 800; font-variant-numeric: tabular-nums; }
.find-foot { margin-top: 14px; padding-top: 13px; border-top: 1px solid var(--gray); }

/* ── Pestaña «Plan» ──────────────────────────────────────────────────────── */
.plan-row { display: flex; align-items: center; gap: 13px; padding: 12px 0; border-bottom: 1px solid var(--gray); }
.plan-row:last-child { border-bottom: 0; }
.plan-num { min-width: 14px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; color: var(--sub); }
.plan-main { flex: 1; min-width: 0; }
.plan-title { font-size: 13px; font-weight: 600; color: var(--navy); }
.plan-sub { font-size: 11px; color: var(--sub); margin-top: 3px; }
/* Los puntos que la acción le quita al score. Verde porque bajar el score es
   la única dirección buena que tiene este número. */
.plan-pts {
  white-space: nowrap;
  font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums;
  border-radius: 20px; padding: 5px 11px;
  background: var(--status-resolved-soft); color: var(--ok);
}
.plan-pts.is-none { background: var(--cream-2); color: var(--sub); }

.proj-val { font-size: 36px; font-weight: 800; color: var(--ok); line-height: 1; }
.proj-unit { font-size: 12px; font-weight: 600; color: var(--sub); margin-left: 8px; }
.proj-bar { display: grid; grid-template-columns: 42px 1fr 30px; align-items: center; gap: 10px; margin-top: 10px; font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; color: var(--sub); }
.proj-track { height: 8px; background: var(--cream-2); border-radius: 4px; overflow: hidden; }
.proj-fill { height: 100%; border-radius: 4px; min-width: 3px; }
.proj-fill.now  { background: var(--navy); }
.proj-fill.then { background: var(--status-resolved); }
.proj-num { font-size: 12px; font-weight: 700; color: var(--navy); text-align: right; font-variant-numeric: tabular-nums; letter-spacing: 0; }

/* ── Pestaña «Tendencia» ─────────────────────────────────────────────────── */
/* Con dos informes una línea de tiempo miente: une dos puntos y sugiere una
   pendiente que no está medida. Hasta que haya seis ciclos se dibujan columnas
   apareadas, que es una comparación y no una proyección. */
.gbar { display: flex; align-items: flex-end; gap: 22px; }
.gbar-group { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 9px; min-width: 0; }
.gbar-pair { display: flex; align-items: flex-end; justify-content: center; gap: 5px; height: 150px; width: 100%; }
.gbar-col { width: 24px; border-radius: 3px 3px 0 0; background: var(--cream-2); }
.gbar-name { font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; }
.gbar-side { flex: 1 1 260px; min-width: 0; }
.gbar-note { font-size: 13px; color: var(--sub); line-height: 1.65; margin-top: 12px; }

/* Antigüedad contra el objetivo: lo que importa de una crítica abierta es
   cuánto lleva, no en qué tramo de tres cae. Los tramos siguen debajo cuando
   hay alguna pasada de 30 días. */
.obj-row { display: grid; grid-template-columns: 74px 1fr 66px; align-items: center; gap: 14px; margin-bottom: 11px; }
.obj-row:last-child { margin-bottom: 0; }
.obj-track { height: 10px; background: var(--cream-2); border-radius: 5px; overflow: hidden; }
.obj-fill { height: 100%; border-radius: 5px; min-width: 3px; }
.obj-val { text-align: right; font-size: 12px; font-weight: 700; color: var(--navy); font-variant-numeric: tabular-nums; }
.obj-val.none { color: var(--ok); font-weight: 600; }
/* Pasada del objetivo: la barra ya llegó al tope y no puede decir cuánto se
   pasó, así que lo dice el número. */
.obj-val.over { color: var(--err); }

/* ── Pestaña «Sitios» ────────────────────────────────────────────────────── */
.expo-cell { min-width: 150px; width: 26%; }
.recon-box {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  background: var(--cream); border-radius: 8px; padding: 15px 18px;
}

/* ── Pestaña «Avance» ────────────────────────────────────────────────────── */
.prev-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
.prev-item { display: flex; align-items: center; gap: 11px; padding: 11px 13px; background: var(--cream); border-radius: var(--r); font-size: 12px; }
.prev-item .sev-dot { flex-shrink: 0; }
.prev-name { flex: 1; min-width: 0; }
.prev-state { flex-shrink: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; font-weight: 700; letter-spacing: 1px; text-transform: uppercase; }

.chip-row { display: flex; gap: 8px; flex-wrap: wrap; }
.chip { font-size: 11px; font-weight: 700; border-radius: 20px; padding: 5px 12px; border: 1px solid var(--gray); background: var(--white); color: var(--navy); white-space: nowrap; }

.kv-list { display: flex; flex-direction: column; gap: 9px; }
.kv-row { display: grid; grid-template-columns: 170px 1fr; gap: 14px; align-items: baseline; padding: 11px 14px; background: var(--cream); border-radius: var(--r); font-size: 12px; }
.kv-key { font-weight: 700; color: var(--navy); }

/* ============================================================================
   19. EXPORTACIÓN DEL INFORME: vista previa del Word
   ========================================================================== */
/* Secciones, portada y textos a la izquierda; a la derecha el documento de
   verdad, dibujado por docx-preview (public/js/informe.js). Portado de la
   pestaña «Informe Word» de assessment_normativo.

   El panel se queda a la vista mientras se lee el documento, y como con los
   textos abiertos es más alto que la pantalla, se desplaza por dentro. */
.inf { display: grid; grid-template-columns: 340px minmax(0, 1fr); gap: 28px; align-items: start; }

.inf-panel {
  position: sticky; top: 20px;
  max-height: calc(100vh - 40px); overflow-y: auto;
  display: flex; flex-direction: column; gap: 2px;
  background: var(--white);
  border-radius: var(--r);
  box-shadow: 0 2px 12px color-mix(in srgb, var(--navy) 9%, transparent);
  padding: 16px 14px;
}
.inf-panel-lbl { padding: 0 8px 8px; }
.inf-lista { display: flex; flex-direction: column; gap: 2px; }

.inf-sec {
  display: flex; align-items: center; gap: 10px;
  padding: 7px 8px; border-radius: var(--r);
  font-size: 13px; font-weight: 600; color: var(--navy);
  cursor: pointer;
}
.inf-sec:hover { background: var(--cream); }
.inf-sec input, .inf-sub input {
  width: 15px; height: 15px; margin: 0; flex-shrink: 0;
  accent-color: var(--navy); cursor: pointer;
}
.inf-sec-fija { cursor: default; color: var(--sub); font-weight: 400; }
.inf-sec-fija:hover { background: none; }
.inf-sec-nom { flex: 1; min-width: 0; }
.con-vista .inf-sec-nom:hover { text-decoration: underline; }
.inf-num { color: var(--sub); font-variant-numeric: tabular-nums; }
.inf-cnt {
  font-size: 11px; font-weight: 700; color: var(--sub);
  background: var(--cream); border-radius: 20px; padding: 1px 8px;
}
/* Desmarcada, o sin nada que imprimir: se apaga y pierde el número. */
.inf-sec-grupo.off .inf-sec,
.inf-sec-grupo.vacia .inf-sec { color: var(--sub); font-weight: 400; }
.inf-vacia { display: none; font-size: 11px; color: var(--sub); white-space: nowrap; }
.inf-sec-grupo.vacia .inf-vacia { display: inline; }
.inf-sec-grupo.vacia .inf-cnt { display: none; }

.inf-subs {
  display: flex; flex-direction: column; gap: 2px;
  margin: 0 0 6px 33px; padding-left: 10px;
  border-left: 2px solid var(--gray);
}
.inf-subs[hidden] { display: none; }
.inf-sub { display: flex; align-items: center; gap: 8px; padding: 3px 0; font-size: 12px; color: var(--sub); cursor: pointer; }

.inf-datos { margin-top: 10px; padding: 12px 8px 0; border-top: 1px solid var(--gray); }
.inf-datos summary { font-size: 13px; font-weight: 700; color: var(--navy); cursor: pointer; }
.inf-datos[open] summary { margin-bottom: 12px; }
.inf-datos .fl { margin-bottom: 14px; }
.inf-datos textarea { font-size: 13px; resize: vertical; }
.inf-textos-lbl { margin: 0 0 12px; }
.inf-kv { display: flex; flex-direction: column; gap: 8px; }
.inf-kv-row { display: flex; flex-direction: column; gap: 2px; font-size: 12px; overflow-wrap: anywhere; }
.inf-editar { display: inline-block; margin-top: 10px; font-size: 12px; }

.inf-descargar { width: 100%; margin-top: 14px; font-size: 14px; padding: 12px 18px; }
.inf-nota { margin-top: 8px; padding: 0 8px; font-size: 11px; line-height: 1.5; color: var(--sub); }

.inf-hoja { min-width: 0; }
.inf-estado { min-height: 18px; margin-bottom: 10px; font-size: 12px; color: var(--sub); }
.inf-err { margin-bottom: 12px; }
.inf-err[hidden] { display: none; }

/* El documento. Lo que docx-preview pinta trae sus propios estilos en línea
   (tamaño de página, márgenes, letra): acá sólo se le quita el fondo gris del
   envoltorio y cada página pasa a ser una hoja blanca sobre el crema. */
.inf-doc { min-height: 60vh; transition: opacity .2s; }
.inf-doc.cargando { opacity: .45; }
.inf-doc:empty { background: var(--white); border-radius: var(--r); box-shadow: 0 2px 12px color-mix(in srgb, var(--navy) 9%, transparent); }
.inf-doc .docx-wrapper { background: none; padding: 0; gap: 24px; align-items: stretch; }
.inf-doc .docx-wrapper > section.docx {
  width: 100% !important; max-width: 100%; margin-bottom: 0;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--navy) 10%, transparent);
}

/* ============================================================================
   20. RESPONSIVE  (§11 — único breakpoint)
   ========================================================================== */
@media (max-width: 800px) {
  /* La barra deja de ocupar columna propia y pasa a ser un panel que entra por
     encima del contenido, con velo detrás. Acá no se contrae —el botón de
     contraer no existe—, así que se deshacen los efectos de .sb-collapsed. */
  .sidebar {
    transform: translateX(-100%);
    transition: transform .2s ease;
    width: var(--sb-w);
  }
  .sb-open .sidebar { transform: translateX(0); }
  /* El velo existe siempre en móvil y se funde con opacity; sin [hidden] a secas. */
  .sidebar-backdrop {
    display: block; opacity: 0; visibility: hidden; pointer-events: none;
    transition: opacity var(--ux-dur) var(--ux-ease), visibility 0s linear var(--ux-dur);
  }
  .sb-open .sidebar-backdrop {
    opacity: 1; visibility: visible; pointer-events: auto;
    transition: opacity var(--ux-dur) var(--ux-ease), visibility 0s;
  }
  .sb-open { overflow: hidden; }
  .sidebar-toggle { display: none; }
  .app-main, .sb-collapsed .app-main { margin-left: 0; }
  .sb-collapsed .sidebar { width: var(--sb-w); }
  .sb-collapsed .sb-label { display: revert; }
  .sb-collapsed .sb-sep { display: none; }
  .sb-collapsed .sidebar-top { flex-direction: row; gap: 8px; padding: 0 6px 2px; }
  .sb-collapsed .sidebar-link { justify-content: flex-start; padding: 10px 12px; }
  .sb-collapsed .btn-logout { padding: 10px 12px; }
  .sb-collapsed .app-badge { padding: 6px 11px; letter-spacing: 2px; }
  .sb-collapsed .app-logo {
    width: auto; height: 38px; padding: 3px 6px;
    object-fit: contain;
  }

  .topbar-mobile {
    display: flex; align-items: center; gap: 12px;
    position: sticky; top: 0; z-index: 40;
    padding: 12px 16px;
    background: var(--navy); color: var(--navy-on);
  }
  .topbar-brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
  .topbar-mobile .app-logo { height: 28px; }
  .topbar-mobile .app-badge { font-size: 11px; padding: 5px 9px; }
  .btn-hamburger {
    display: flex; border: 0; background: transparent;
    color: var(--navy-on); font-size: 21px; line-height: 1; padding: 0; cursor: pointer;
    position: relative;
  }
  /* El icono mide 21 px; el área que recibe el toque, 45. Invisible, para no
     cambiar la barra aprobada. */
  .btn-hamburger::before { content: ''; position: absolute; inset: -12px; }

  .page { padding: 18px 16px 30px; }
  .cl-row, .user-row { flex-wrap: wrap; padding: 12px 16px; }
  .pick-page { margin: 16px auto; padding: 0 16px; }
  .kpi-grid, .dash-grid, .scans-layout, .diff-cols,
  .sum-cards, .aging-grid, .mttr-grid,
  .dash-top, .tab-split, .tab-half, .prev-grid { grid-template-columns: 1fr; }
  .span-4, .span-6, .span-8, .span-12 { grid-column: span 1; }
  .donut-row { flex-direction: column; align-items: flex-start; }

  /* El titular pasa a una columna: el filo que separa las cifras deja de
     tener a qué separar y se convierte en la raya de arriba. */
  .hero { padding: 18px; gap: 18px; }
  .hero-title { font-size: 24px; }
  .hero-figs { padding: 16px 0 0; border-left: 0; border-top: 1px solid var(--gray); width: 100%; }
  /* Cinco pestañas no caben en el ancho de un teléfono: la barra se desplaza
     en vez de partirse en dos filas, que dejaría la línea inferior suelta. */
  .tab-bar { overflow-x: auto; }
  .tab-bar .tabs { min-width: max-content; }
  .gbar { gap: 12px; }

  /* Suelo de la tabla, que es lo que hace útil al .table-wrap.
     Con `width:100%` y sin mínimo, la tabla se encoge hasta caber siempre: el
     `overflow-x:auto` del contenedor no se activa nunca y lo que ocurre en un
     teléfono no es desplazamiento sino ocho columnas aplastadas a dos letras
     por celda. Con el mínimo, la tabla conserva sus proporciones y se
     desplaza, que es lo que §7.10 pide.

     Va dentro del breakpoint a propósito: en escritorio el contenedor siempre
     es más ancho que este valor, así que fijarlo fuera no cambiaría nada y sí
     arriesgaría a recortar una tabla dentro de un panel estrecho.

     `--table-min` deja que una tabla de dos columnas lo baje sin tocar esta
     regla. */
  .data-table { min-width: var(--table-min, 560px); }

  /* La vista previa del Word: el panel va arriba y deja de seguir al
     documento, que en un teléfono se lee debajo. */
  .inf { grid-template-columns: 1fr; }
  .inf-panel { position: static; max-height: none; overflow: visible; }
}

/* ============================================================================
   21. IMPRESIÓN  (§7 #14 del estándar común — el bloque de `rat`)
   ==========================================================================
   Esta aplicación se imprime: el informe, su triaje y el anexo son lo que se
   lleva a la reunión con el cliente cuando todavía no se generó el .docx, y
   hasta ahora imprimirlos sacaba la barra lateral navy a toda página por la
   izquierda y el contenido aplastado en la columna que sobraba.

   Lo que desaparece es la navegación y los mandos —no existen en papel— y lo
   que se conserva es el color que codifica significado: la paleta de severidad
   viene del .docx que firma el cliente, así que un «Crítico» impreso en gris
   sería un dato perdido, no un ahorro de tinta. De ahí el print-color-adjust
   sobre las piezas de dominio y sólo sobre ellas. */
@media print {
  body { background: #fff; }

  /* Navegación, velos y mandos. */
  .skip-link,
  .sidebar,
  .sidebar-backdrop,
  .toasts,
  .topbar-mobile,
  .pick-bar,
  .filters,
  .fl-actions,
  .hdr-actions,
  .pagination,
  .mode-tabs,
  .tabs,
  .btn,
  .btn-link,
  .sidebar-toggle { display: none !important; }

  /* El chasis deja de ser chasis: sin barra no hay columna que reservar. */
  .app { display: block; }
  .app-main, .sb-collapsed .app-main { margin-left: 0; }
  .page, .pick-page { max-width: none; margin: 0; padding: 0; gap: 14px; }

  /* Las superficies pierden la sombra —no se imprime— y ganan un filo que sí.
     `break-inside` mantiene entera cada tarjeta y cada fila de tabla: partir
     un hallazgo entre dos páginas deja media descripción huérfana al pie, que
     es el mismo defecto que el .docx evita con <w:cantSplit/>. */
  .card, .module-body, .kpi-card, .sk-caja, .hero, .fig-card, .side-box {
    box-shadow: none;
    break-inside: avoid;
    page-break-inside: avoid;
  }
  .table-wrap { overflow-x: visible; }
  .data-table { min-width: 0; }
  tr, .cl-row, .user-row, .diff-item, .scan-item { break-inside: avoid; page-break-inside: avoid; }
  .module-title, .card-title, .detail-title { break-after: avoid; page-break-after: avoid; }

  /* Dominio (nivel 3): el color ES el dato. Se imprime tal cual. */
  .risk-badge, .sev-dot, .tag, .module-head, .sum-card, .aging-cell,
  .hero-lamp, .cmp-bar, .gbar-col, .obj-fill, .proj-fill, .one-act {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }

  /* Exportación: se imprime el documento, no los mandos. */
  .inf { display: block; }
  .inf-panel, .inf-estado { display: none !important; }

  /* El titular y la fila que lo sigue no se parten. */
  .hero, .one-act, .find-row, .plan-row, .prev-item { break-inside: avoid; page-break-inside: avoid; }
}
