/* ============================================================
   SIGEO — sistema de diseño
   Sin build step: CSS plano, breakpoints como literales.
   Breakpoints: <768px móvil · 768–1023px tablet · ≥1024px desktop
   ============================================================ */

/* ---------- 1. Tokens ---------- */
:root {
  /* rampa azul cruda — nunca se usa directo en componentes */
  --b50:#eff6ff;  --b100:#dbeafe; --b200:#bfdbfe; --b300:#93c5fd;
  --b400:#60a5fa; --b500:#3b82f6; --b600:#2563eb; --b700:#1d4ed8;
  --b800:#1e40af; --b900:#1e3a8a; --b950:#172554;

  --n0:#ffffff; --n50:#f8fafc; --n100:#f1f5f9; --n200:#e2e8f0; --n300:#cbd5e1;
  --n400:#94a3b8; --n500:#64748b; --n600:#475569; --n700:#334155;
  --n800:#1e293b; --n900:#0f172a; --n950:#020617;

  color-scheme: light;

  /* semánticos — los componentes solo usan estos */
  --bg:            var(--n100);
  --surface:       var(--n0);
  --surface-2:     var(--n50);
  --surface-3:     var(--n100);
  --border:        var(--n200);
  --border-strong: var(--n300);

  --text:      var(--n800);
  --text-soft: var(--n500);
  --text-inv:  var(--n0);

  --brand:       var(--b600);
  --brand-hover: var(--b700);
  --brand-soft:  var(--b50);
  --brand-text:  var(--b700);
  --on-brand:    #ffffff;

  --ok-bg:#dcfce7;    --ok-text:#166534;   --ok-border:#86efac;
  --error-bg:#fee2e2; --error-text:#991b1b;--error-border:#fca5a5;
  --warn-bg:#fffbeb;  --warn-text:#92400e; --warn-border:#fcd34d;
  --info-bg:var(--b50); --info-text:var(--b700); --info-border:var(--b200);
  /* Rojo de gráfico (trazos/rellenos), NO de texto: --error-text es muy
     oscuro para leer como badge/alerta, pero en un gráfico queda casi
     indistinguible de --warn-text (validado con dataviz/validate_palette.js:
     ΔE 6.7 en visión normal, contra un piso de 15). Mismo rojo semántico,
     tono más vivo para uso gráfico. */
  --error-chart: #ef4444;
  /* Licencias: gris en claro, ámbar/dorado en oscuro — a propósito
     distinto por modo (no una sombra automática del mismo tono). En
     oscuro reusa el ámbar de --warn-bg/--warn-text (ver bloques de modo
     oscuro más abajo), que ahí sí se ve bien y no compite con Enfermedad
     (azul) ni ART (rojo). */
  --licencia-bg: var(--surface-3);
  --licencia-text: var(--text-soft);

  /* Paleta categórica para identidad visual de módulos (íconos del menú,
     tarjetas KPI genéricas) — NO para gráficos de datos, que siguen usando
     categoria_ausencia_info() y sus propios tokens semánticos. 8 tonos,
     orden fijo, ya validado (skill dataviz/references/palette.md: peor par
     adyacente ΔE 9.1 claro / 8.4 oscuro en daltonismo, contra un piso de 8).
     No ciclar el orden ni usar un tono fuera de esta lista. */
  --tono-blue:    #2a78d6;
  --tono-orange:  #eb6834;
  --tono-aqua:    #1baf7a;
  --tono-yellow:  #eda100;
  --tono-magenta: #e87ba4;
  --tono-green:   #008300;
  --tono-violet:  #4a3aa7;
  --tono-red:     #e34948;

  --ring: 0 0 0 3px rgba(37, 99, 235, .35); /* fallback plano antes del color-mix */
  --sh1: 0 1px 2px rgb(15 23 42 / .06), 0 1px 3px rgb(15 23 42 / .10);
  --sh2: 0 4px 12px rgb(15 23 42 / .08);
  --sh3: 0 12px 32px rgb(15 23 42 / .18);

  --r-sm:6px; --r:10px; --r-lg:14px; --r-full:999px;
  --sp1:4px; --sp2:8px; --sp3:12px; --sp4:16px; --sp5:24px; --sp6:32px; --sp7:48px;
  --control-h:44px;
  --sidebar-w:248px;
  --topbar-h:56px;
  --tabbar-h:56px;
  --font: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --tr:150ms ease;
}

@supports (--x: color-mix(in srgb, red, blue)) {
  :root { --ring: 0 0 0 3px color-mix(in srgb, var(--brand) 35%, transparent); }
}

/* Modo oscuro automático — solo si el usuario no forzó "light" manualmente */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --bg:#020617; --surface:#0f172a; --surface-2:#15213a; --surface-3:#1c2a45;
    --border:#25324d; --border-strong:#33425f;
    --text:#f1f5f9; --text-soft:#94a3b8; --text-inv:#0f172a;
    --brand:#60a5fa; --brand-hover:#93c5fd; --brand-soft:#0e2340;
    --brand-text:#93c5fd; --on-brand:#0f172a;
    --ok-bg:#052e1a;   --ok-text:#86efac;   --ok-border:#166534;
    --error-bg:#3f1212; --error-text:#fca5a5; --error-border:#991b1b;
    --warn-bg:#3a2a06;  --warn-text:#fcd34d;  --warn-border:#92400e;
    --info-bg:#14243f;  --info-text:#93c5fd;  --info-border:#1e40af;
    /* En oscuro --error-text ya se distingue bien de --warn-text (validado
       aparte), no hace falta un tono extra como en claro. */
    --error-chart: #fca5a5;
    /* Licencias vuelve a ámbar acá (a diferencia de claro, que es gris). */
    --licencia-bg: var(--warn-bg);
    --licencia-text: var(--warn-text);
    --tono-blue:    #3987e5;
    --tono-orange:  #d95926;
    --tono-aqua:    #199e70;
    --tono-yellow:  #c98500;
    --tono-magenta: #d55181;
    --tono-green:   #008300;
    --tono-violet:  #9085e9;
    --tono-red:     #e66767;
    --sh1:0 1px 2px rgb(0 0 0/.5); --sh2:0 4px 12px rgb(0 0 0/.45); --sh3:0 12px 32px rgb(0 0 0/.6);
  }
}

/* Modo oscuro manual — MISMAS declaraciones que el bloque de arriba. Va último
   a propósito: empata en especificidad con el bloque @media y gana por orden. */
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg:#020617; --surface:#0f172a; --surface-2:#15213a; --surface-3:#1c2a45;
  --border:#25324d; --border-strong:#33425f;
  --text:#f1f5f9; --text-soft:#94a3b8; --text-inv:#0f172a;
  --brand:#60a5fa; --brand-hover:#93c5fd; --brand-soft:#0e2340;
  --brand-text:#93c5fd; --on-brand:#0f172a;
  --ok-bg:#052e1a;   --ok-text:#86efac;   --ok-border:#166534;
  --error-bg:#3f1212; --error-text:#fca5a5; --error-border:#991b1b;
  --warn-bg:#3a2a06;  --warn-text:#fcd34d;  --warn-border:#92400e;
  --info-bg:#14243f;  --info-text:#93c5fd;  --info-border:#1e40af;
  --error-chart: #fca5a5;
  --licencia-bg: var(--warn-bg);
  --licencia-text: var(--warn-text);
  --tono-blue:    #3987e5;
  --tono-orange:  #d95926;
  --tono-aqua:    #199e70;
  --tono-yellow:  #c98500;
  --tono-magenta: #d55181;
  --tono-green:   #008300;
  --tono-violet:  #9085e9;
  --tono-red:     #e66767;
  --sh1:0 1px 2px rgb(0 0 0/.5); --sh2:0 4px 12px rgb(0 0 0/.45); --sh3:0 12px 32px rgb(0 0 0/.6);
}

/* ---------- 2. Reset / base ---------- */
*, *::before, *::after { box-sizing: border-box; }
html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg); /* evita la franja blanca del navegador detrás de la scrollbar */
  scrollbar-color: var(--border-strong) var(--bg); /* Firefox */
}
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: var(--bg); }
::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: var(--r-full); border: 3px solid var(--bg); }
::-webkit-scrollbar-thumb:hover { background: var(--text-soft); }
body {
  margin: 0;
  min-height: 100vh; min-height: 100dvh;
  background: var(--bg); color: var(--text);
  font-family: var(--font);
  font-size: 16px; line-height: 1.5;
}
h1, h2, h3 { margin: 0 0 var(--sp2); color: var(--brand-text); line-height: 1.25; }
h1 { font-size: 1.5rem; } h2 { font-size: 1.15rem; }
a, a:visited { color: var(--brand-text); }
img { max-width: 100%; display: block; }
:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* Un <svg> de icono() sin contenedor que lo limite (.btn, .nav-link, etc.)
   no tiene ancho/alto propio: el navegador lo estira al 100% del bloque
   contenedor y calcula el alto por el aspect-ratio del viewBox, gigante.
   Para un ícono suelto en medio de texto (ej. un link con <?= icono(...) ?>
   sin envolver en .btn), usar icono('nombre', 'icono-linea'). */
.icono-linea { width: 1.1em; height: 1.1em; vertical-align: -0.2em; fill: none; stroke: currentColor; stroke-width: 1.8; flex-shrink: 0; }

.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--brand); color: var(--on-brand);
  padding: var(--sp2) var(--sp4); border-radius: var(--r-sm);
}
.skip-link:focus { left: var(--sp3); top: var(--sp3); }

html.sin-scroll { overflow: hidden; }

/* ---------- 2b. Layout auth (login/setup) ---------- */
body.auth { display: flex; align-items: center; justify-content: center; min-height: 100vh; min-height: 100dvh; padding: var(--sp5); }
.auth-caja { position: relative; width: 100%; max-width: 400px; }
.auth-caja__tema { position: absolute; top: -52px; right: 0; }
.auth-caja__tarjeta { text-align: center; margin-bottom: 0; }
.auth-caja h1 { text-align: center; }

/* ---------- 3. Shell ---------- */
.app { min-height: 100vh; min-height: 100dvh; }

.topbar {
  display: flex;
  align-items: center; gap: var(--sp3);
  height: var(--topbar-h); padding: 0 var(--sp4);
  background: var(--surface); border-bottom: 1px solid var(--border);
  position: sticky; top: 0; z-index: 30;
}
.topbar__marca { font-weight: 700; color: var(--brand-text); }
.topbar__usuario { display: flex; align-items: center; gap: var(--sp2); margin-left: auto; }
.topbar__avatar {
  display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  width: 32px; height: 32px; border-radius: var(--r-full);
  background: var(--brand); color: var(--on-brand);
  font-size: .8rem; font-weight: 700;
}
.topbar__nombre { color: var(--text-soft); font-size: .9rem; }
@media (max-width: 639px) { .topbar__nombre { display: none; } }
@media (min-width: 1024px) {
  .topbar .topbar__marca { display: none; }
}

.btn-icono, .btn-icono:visited {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h); height: var(--control-h);
  border-radius: var(--r-sm); background: none; border: 0; color: var(--text); cursor: pointer;
  text-decoration: none;
}
.btn-icono:hover { background: var(--surface-2); }
.btn-icono svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; }

/* Botón de solo-ícono para acciones destructivas en filas de tabla (ej.
   "Quitar" un postulante): más chico que .btn-icono, siempre en rojo, sin
   el texto — el ícono de tacho ya es autoexplicativo en ese contexto. */
.btn-icono--peligro {
  display: inline-flex; align-items: center; justify-content: center;
  width: 36px; height: 36px; padding: 0;
  border-radius: var(--r-sm); border: 1px solid var(--error-border); background: var(--error-bg);
  color: var(--error-text); cursor: pointer;
}
.btn-icono--peligro:hover { background: var(--error-text); color: #fff; }
.btn-icono--peligro svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; }

.sidebar {
  display: none;
  flex-direction: column;
  /* Oscura siempre, sea cual sea el tema claro/oscuro de la app — igual que
     el menú de navegación del mobile (#drawer, ver más abajo): es la marca
     del "marco" de la app, no contenido que deba seguir el tema. */
  background: var(--n900); border-right: 1px solid var(--n800);
  /* Alto fijo al viewport, NO stretch al alto de la fila del grid: con
     align-self:stretch + min-height:100%, una página con el panel de
     detalle largo (ej. la ficha de Personas) estira el sidebar a miles de
     px, empujando el botón de colapsar (margin-top:auto) fuera de la
     pantalla. position:sticky ya lo mantiene anclado al hacer scroll. */
  height: 100vh; height: 100dvh; overflow-y: auto; align-self: start;
  position: sticky; top: 0;
  padding: var(--sp5) var(--sp3);
}
.sidebar__marca { font-weight: 700; font-size: 1.2rem; color: #fff; padding: 0 var(--sp3) var(--sp5); text-align: center; }
.topbar__colapsar svg { transition: transform 150ms ease; }

.sidebar__nav { display: flex; flex-direction: column; gap: 2px; flex: 1; }

.nav-link, .nav-link:visited {
  display: flex; align-items: center; gap: var(--sp3);
  padding: var(--sp3); border-radius: var(--r);
  color: var(--n300); text-decoration: none; font-size: .95rem;
  min-height: var(--control-h);
}
.nav-link:hover { background: var(--n800); color: #fff; }
.nav-link svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 1.8; flex-shrink: 0; color: var(--nav-tono, currentColor); }

/* Identidad de color por módulo (nav_items() -> 'color', ver partials/nav.php
   y la tabbar en layouts/app.php): --nav-tono es una variable, no un color
   directo — la usan tanto el ícono (arriba) como la pastilla activa (abajo),
   así que la clase va en el .nav-link/.tabbar__item que envuelve al ícono,
   no en el <svg> suelto. */
.nav-color--blue    { --nav-tono: var(--tono-blue); }
.nav-color--orange  { --nav-tono: var(--tono-orange); }
.nav-color--aqua    { --nav-tono: var(--tono-aqua); }
.nav-color--yellow  { --nav-tono: var(--tono-yellow); }
.nav-color--magenta { --nav-tono: var(--tono-magenta); }
.nav-color--green   { --nav-tono: var(--tono-green); }
.nav-color--violet  { --nav-tono: var(--tono-violet); }
.nav-color--red     { --nav-tono: var(--tono-red); }

/* Ítem activo: pastilla rellena con el color propio del módulo (o la marca
   si no tiene uno asignado), no siempre el mismo azul para todo. */
.nav-link[aria-current="page"], .nav-link:visited[aria-current="page"] {
  background: var(--nav-tono, var(--brand)); color: #fff; font-weight: 600;
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
}
.nav-link[aria-current="page"] svg { color: #fff; }

/* Grupo colapsable del menú: <details> nativo, sin JS — cierra/abre solo,
   accesible por teclado, y el estado (abierto/cerrado) no hay que
   guardarlo en ningún lado porque el propio <details> ya lo mantiene. */
.nav-grupo summary { list-style: none; cursor: pointer; }
.nav-grupo summary::-webkit-details-marker,
.nav-grupo summary::marker { display: none; content: ""; }
.nav-grupo__flecha { width: 16px !important; height: 16px !important; margin-left: auto; transition: transform 150ms ease; }
.nav-grupo[open] .nav-grupo__flecha { transform: rotate(180deg); }
.nav-grupo__hijos { display: flex; flex-direction: column; gap: 2px; margin-top: 2px; }
.nav-link--hijo { padding-left: calc(20px + var(--sp3) + var(--sp3)); font-size: .9rem; }

.contenido { max-width: 900px; margin-inline: auto; padding: var(--sp6) var(--sp5); }
.contenido--ancho { max-width: 1200px; }
.contenido--completo { max-width: none; }

.tabbar {
  display: none;
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30;
  background: var(--surface); border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.tabbar__lista { display: flex; }
.tabbar__item, .tabbar__item:visited {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 2px; min-height: 48px; padding: var(--sp2) 0;
  color: var(--text-soft); text-decoration: none; font-size: .68rem;
}
.tabbar__item svg { width: 22px; height: 22px; fill: none; stroke: currentColor; stroke-width: 1.8; color: var(--nav-tono, currentColor); }
.tabbar__item[aria-current="page"], .tabbar__item:visited[aria-current="page"] { color: var(--nav-tono, var(--brand-text)); font-weight: 600; }
.tabbar__item[aria-current="page"] svg { color: var(--nav-tono, var(--brand-text)); }

/* Botón flotante "Reportar un error": visible en toda la app, abre el
   drawer de formulario como cualquier .js-nueva-panel. Se levanta por
   encima de la tabbar en mobile para no quedar tapado. */
.btn-reportar-error {
  position: fixed; right: var(--sp4); bottom: calc(var(--sp4) + 8px); z-index: 25;
  display: flex; align-items: center; justify-content: center;
  width: 48px; height: 48px; border-radius: var(--r-full);
  background: var(--warn-bg); color: var(--warn-text); border: 1px solid var(--warn-border);
  box-shadow: 0 4px 14px rgba(2, 6, 23, .18);
  text-decoration: none;
}
.btn-reportar-error:visited { color: var(--warn-text); }
.btn-reportar-error:hover { filter: brightness(0.97); }
.btn-reportar-error svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.8; }
@media (max-width: 767px) {
  .btn-reportar-error { bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px) + var(--sp3)); }
}

/* drawer: <dialog> nativo */
.drawer { padding: 0; border: 0; margin: 0; margin-right: auto; height: 100vh; height: 100dvh; max-height: 100dvh; width: min(320px, 86vw); background: var(--surface); }
.drawer::backdrop { background: rgba(2, 6, 23, .55); }
/* Menú de navegación mobile (#drawer): mismo tono oscuro fijo que .sidebar
   de escritorio — es el mismo "marco" de la app, no depende del tema. */
#drawer { background: var(--n900); }
#drawer .drawer__cerrar { color: var(--n300); }
#drawer .drawer__cerrar:hover { background: var(--n800); color: #fff; }
.drawer__panel { display: flex; flex-direction: column; height: 100%; padding: var(--sp5) var(--sp3); }
.drawer__cerrar { align-self: flex-end; }
@media (prefers-reduced-motion: no-preference) {
  .drawer[open] { animation: drawer-entrar 200ms ease; }
  @keyframes drawer-entrar { from { transform: translateX(-100%); } to { transform: translateX(0); } }

  /* View Transitions API (app.js: conTransicionVisual): cross-fade entre el
     estado viejo y el nuevo al abrir/cerrar un drawer o cambiar de panel.
     Misma duración que drawer-entrar arriba para que no se sientan
     desincronizados. Sin soporte del navegador, esta regla no aplica nada
     (los pseudo-elementos ::view-transition-* no existen) y solo queda el
     slide de arriba — nunca rompe, es una mejora que se apila encima. */
  ::view-transition-old(root),
  ::view-transition-new(root) { animation-duration: 200ms; }
}

/* Drawer de formularios (alta/edición): mismo <dialog> nativo, pegado al
   borde DERECHO en vez del izquierdo, y bastante más ancho porque adentro
   va un formulario, no un menú. La página (lista + detalle) queda visible
   detrás, atenuada por el ::backdrop heredado de .drawer. */
.drawer--formulario {
  margin-right: 0;
  margin-left: auto;
  width: min(680px, 94vw);
  overflow-y: auto;
}
.drawer--formulario .drawer__panel { padding: var(--sp5); }

/* Drawer "hijo": se apila sobre el drawer de formulario (o sobre la ficha
   normal si no hay ninguno abierto) para editar un dato relacionado sin
   perder lo que había atrás — ej. abrir la ficha de Persona desde el
   formulario de Postulante, y desde ahí un teléfono o domicilio puntual.
   Es más angosto a propósito: deja ver el borde de lo que quedó detrás. */
.drawer--hijo { width: min(620px, 92vw); box-shadow: -18px 0 46px rgba(2, 6, 23, .35); }
/* El "volver" de arriba (pensado para cuando el formulario cae a página
   completa sin JS) es redundante acá: el drawer ya tiene su "Cancelar". */
.drawer--formulario .md-solo-pagina { display: none; }
@media (prefers-reduced-motion: no-preference) {
  .drawer--formulario[open] { animation: drawer-formulario-entrar 220ms ease; }
  @keyframes drawer-formulario-entrar { from { transform: translateX(100%); } to { transform: translateX(0); } }
}

/* ---------- breakpoints del shell ---------- */
@media (min-width: 768px) {
  .contenido { padding: var(--sp5) var(--sp4); }
}
@media (min-width: 1024px) {
  .app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; }
  /* Sin esto, un grid item no se achica por debajo del ancho de su
     contenido (default min-width:auto) — una tabla ancha (ej. el
     listado de Empleados propios, 19 columnas) terminaba estirando TODA
     la página en vez de quedar contenida por el overflow-x del propio
     .tabla-wrap. */
  .app__panel { min-width: 0; }
  .sidebar { display: flex; }

  /* Sidebar: expandido (default) <-> oculto. Un solo botón en la topbar
     alterna entre los dos — nunca desaparece, así que siempre hay cómo
     volver. */
  :root[data-sidebar="oculto"] .topbar__colapsar svg { transform: rotate(180deg); }
  :root[data-sidebar="oculto"] .sidebar { display: none; }
  :root[data-sidebar="oculto"] .app { grid-template-columns: 1fr; }
}
@media (max-width: 767px) {
  .tabbar { display: block; }
  body { padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom, 0px)); }
  .contenido { padding: var(--sp4) var(--sp3); }
}

/* ---------- 4. Componentes ---------- */
.card { background: var(--surface); border-radius: var(--r-lg); padding: var(--sp5); box-shadow: var(--sh1); margin-bottom: var(--sp5); }
@media (max-width: 767px) { .card { border-radius: var(--r); padding: var(--sp4); } }

.page-header { display: flex; justify-content: space-between; align-items: center; gap: var(--sp3); flex-wrap: wrap; margin-bottom: var(--sp4); }
.page-header h1 { margin: 0; }
.volver-modulo { margin-bottom: var(--sp3); }
.cierres-filtro { display: flex; justify-content: flex-end; margin: calc(var(--sp3) * -1) 0 var(--sp4); }
@media (max-width: 767px) { .cierres-filtro { justify-content: flex-start; margin-top: 0; } }
.acciones-cabecera { display: flex; align-items: center; gap: var(--sp2); flex-wrap: wrap; }

/* Fila de encabezado de grupo (ej. fecha) dentro de una tabla de listado. */
.fila-grupo td { background: var(--surface-2, var(--brand-soft)); padding: var(--sp2) var(--sp3); }

/* Acordeón (Campañas): la fila expande su detalle debajo, en vez de navegar o abrir un panel lateral. */
.tabla-acordeon tbody tr.fila-acordeon { cursor: pointer; }
.fila-acordeon:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.expand-icon {
  display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  border-radius: var(--r-full); border: 1px solid var(--border-strong); color: var(--text-soft);
  transition: transform 200ms ease, color 150ms ease, border-color 150ms ease;
}
.expand-icon svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2.4; }
.fila-acordeon.open .expand-icon { transform: rotate(180deg); color: var(--brand-text); border-color: var(--brand); }
.fila-acordeon-detalle { display: none; }
.fila-acordeon-detalle.visible { display: table-row; }
.fila-acordeon-detalle > td { background: var(--surface-2, var(--bg)); padding: var(--sp4); }
.acordeon-contenido[aria-busy="true"] { min-height: 60px; }

/* Tarjetas de número grande para los dashboards por rol (Inicio). */
.kpis { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--sp4); margin-bottom: var(--sp5); }
/* Reporte de ausentismo: 4 columnas fijas (Licencias/Enfermedad/ART/Acumulado) x 3 filas (horas/días/empleados) — no reacomodar según ancho disponible. */
.kpis--4col { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 767px) { .kpis--4col { grid-template-columns: repeat(2, 1fr); } }

/* Torta + leyenda a la izquierda, tarjetas ocupando el resto de la fila — mismo layout que el reporte de origen. */
.reporte-resumen { display: flex; align-items: stretch; gap: var(--sp5); flex-wrap: wrap; }
.reporte-resumen__torta { flex: none; display: flex; align-items: center; }
.reporte-resumen__kpis { flex: 1; margin-bottom: 0; min-width: 280px; }
@media (max-width: 900px) { .reporte-resumen__torta { width: 100%; } }
.kpi { position: relative; background: var(--surface); border-radius: var(--r-lg); padding: var(--sp5); box-shadow: var(--sh1); text-align: center; }
.kpi__icono {
  position: absolute; top: var(--sp3); right: var(--sp3);
  width: 36px; height: 36px; border-radius: var(--r-full);
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in oklab, var(--kpi-tono, var(--brand)) 22%, transparent);
  color: var(--kpi-tono, var(--brand));
}
.kpi__icono svg { width: 18px; height: 18px; fill: none; stroke: currentColor; stroke-width: 1.8; }
.kpi__valor { display: block; font-size: 2.4rem; font-weight: 700; color: var(--brand-text); line-height: 1.1; }
.kpi__etiqueta { display: block; margin-top: var(--sp2); color: var(--text-soft); font-size: .9rem; }

/* Variantes de color por categoría (Reporte de ausentismo): mismos tokens
   que ya usan las badges e y el gráfico de barras de la misma página, para
   que la identidad de cada categoría se vea igual en todos lados. */
.kpi--art { background: var(--error-bg); --kpi-tono: var(--error-text); }
.kpi--art .kpi__valor { color: var(--error-text); }
.kpi--art .kpi__etiqueta { color: var(--error-text); opacity: .8; }
.kpi--enfermedad { background: var(--brand-soft); --kpi-tono: var(--brand-text); }
.kpi--enfermedad .kpi__valor { color: var(--brand-text); }
.kpi--enfermedad .kpi__etiqueta { color: var(--brand-text); opacity: .8; }
.kpi--licencia { background: var(--licencia-bg); --kpi-tono: var(--licencia-text); }
.kpi--licencia .kpi__valor { color: var(--licencia-text); }
.kpi--licencia .kpi__etiqueta { color: var(--licencia-text); opacity: .8; }
.kpi--ok { background: var(--ok-bg); --kpi-tono: var(--ok-text); }
.kpi--ok .kpi__valor { color: var(--ok-text); }
.kpi--ok .kpi__etiqueta { color: var(--ok-text); opacity: .8; }

/* Variantes genéricas (Inicio y cualquier otro dashboard sin categoría
   semántica propia): mismos 8 tonos que los íconos del menú (.nav-color--),
   fondo mezclado con color-mix() en vez de un token de fondo a mano por
   tono — evita duplicar 16 tokens más (8 tonos x 2 modos) para un matiz
   que ya se puede calcular. */
.kpi--tono-blue, .kpi--tono-orange, .kpi--tono-aqua, .kpi--tono-yellow,
.kpi--tono-magenta, .kpi--tono-green, .kpi--tono-violet, .kpi--tono-red {
  background: color-mix(in oklab, var(--kpi-tono) 14%, var(--surface));
}
.kpi--tono-blue    { --kpi-tono: var(--tono-blue); }
.kpi--tono-orange  { --kpi-tono: var(--tono-orange); }
.kpi--tono-aqua    { --kpi-tono: var(--tono-aqua); }
.kpi--tono-yellow  { --kpi-tono: var(--tono-yellow); }
.kpi--tono-magenta { --kpi-tono: var(--tono-magenta); }
.kpi--tono-green   { --kpi-tono: var(--tono-green); }
.kpi--tono-violet  { --kpi-tono: var(--tono-violet); }
.kpi--tono-red     { --kpi-tono: var(--tono-red); }
.kpi--tono-blue .kpi__valor, .kpi--tono-orange .kpi__valor, .kpi--tono-aqua .kpi__valor,
.kpi--tono-yellow .kpi__valor, .kpi--tono-magenta .kpi__valor, .kpi--tono-green .kpi__valor,
.kpi--tono-violet .kpi__valor, .kpi--tono-red .kpi__valor { color: var(--kpi-tono); }
.kpi--tono-blue .kpi__etiqueta, .kpi--tono-orange .kpi__etiqueta, .kpi--tono-aqua .kpi__etiqueta,
.kpi--tono-yellow .kpi__etiqueta, .kpi--tono-magenta .kpi__etiqueta, .kpi--tono-green .kpi__etiqueta,
.kpi--tono-violet .kpi__etiqueta, .kpi--tono-red .kpi__etiqueta { color: var(--kpi-tono); opacity: .8; }

/* Gráfico de torta (distribución por categoría) — Chart.js sobre <canvas>,
   ver assets/graficos.js. El contenedor fija el alto porque Chart.js con
   maintainAspectRatio:false necesita un padre con altura explícita. */
.grafico-torta { display: flex; align-items: center; gap: var(--sp6); flex-wrap: wrap; }
.grafico-torta__leyenda { display: flex; flex-direction: column; gap: var(--sp3); }
.grafico-torta__item { display: flex; align-items: center; gap: var(--sp3); }
.grafico-torta__punto { width: 14px; height: 14px; border-radius: var(--r-full); flex: none; }
.grafico-torta__texto { color: var(--text); }
.grafico-torta__porcentaje { color: var(--text-soft); font-size: .85rem; }

/* Gráfico de barras apiladas (Ausentismo mes a mes) — Chart.js sobre <canvas>. */
.grafico-chart { position: relative; width: 100%; }
.grafico-chart--torta { height: 160px; width: 160px; flex: none; }
.grafico-chart--barras { height: 220px; margin-top: var(--sp2); }

/* Tabla de datos debajo del gráfico apilado — un valor exacto por mes y
   categoría (como la tabla de series que deja Excel debajo de un gráfico),
   complementa los tooltips del gráfico en vez de depender solo de ellos. */
.tabla--leyenda-grafico { margin-top: var(--sp4); font-size: .8rem; }
.tabla--leyenda-grafico th, .tabla--leyenda-grafico td { text-align: center; white-space: nowrap; padding: var(--sp2); }
.tabla--leyenda-grafico td:first-child, .tabla--leyenda-grafico th:first-child { text-align: left; }
.tabla--leyenda-grafico td:first-child { display: flex; align-items: center; gap: var(--sp2); }
.tabla--leyenda-grafico i { width: 12px; height: 12px; border-radius: 3px; display: inline-block; flex: none; }
.tabla--leyenda-grafico__total td { font-weight: 700; border-top: 1px solid var(--border); }

/* Dos gráficos lado a lado (Resumen diario): se apilan en pantallas angostas.
   align-items: stretch para que ambas cards de la fila midan lo mismo de
   alto — la de la izquierda (dos tortas) se reparte ese alto entre las dos. */
.fila-graficos { display: grid; gap: var(--sp4); grid-template-columns: 1fr; align-items: stretch; margin-bottom: var(--sp5); }
@media (min-width: 900px) {
  /* 1/3 para las tortas, 2/3 para el mensual: con 50/50 el mensual (12
     meses) quedaba apretado y las tortas sobraban de ancho vacío. */
  .fila-graficos { grid-template-columns: 1fr 2fr; }
}
.fila-graficos > .card { margin: 0; }

/* Dos gráficos de torta apilados en una misma card (Resumen diario:
   ausentes por categoría y horas por categoría), repartiendo el alto de la
   card entre los dos en vez de un tamaño fijo. */
.card--dos-tortas { display: flex; flex-direction: column; }
.card--dos-tortas__fila { flex: 1; display: flex; align-items: center; justify-content: center; padding: var(--sp3) 0; }
.card--dos-tortas__fila:not(:last-child) { border-bottom: 1px solid var(--border); }

/* Mini gráfico de tendencia (grafico_lineas() en PHP) — histórico de un
   signo vital en la ficha de Persona. SVG inline, sin JS; el <title> de
   cada punto da el tooltip nativo del navegador. */
.grafico-lineas { display: flex; flex-direction: column; gap: var(--sp2); }
.grafico-lineas__svg { width: 100%; height: 84px; display: block; overflow: visible; }
.grafico-lineas--grande .grafico-lineas__svg { height: 180px; }
.grafico-lineas--grande .grafico-lineas__etiqueta { font-size: 13px; }
.grafico-lineas__banda { fill: var(--ok-bg); opacity: .6; }
.grafico-lineas__linea { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.grafico-lineas__punto { stroke: var(--surface); stroke-width: 1; cursor: default; }
.grafico-lineas__etiqueta { font-size: 9px; font-weight: 700; dominant-baseline: middle; }
.grafico-lineas__leyenda { display: flex; gap: var(--sp4); font-size: .8rem; color: var(--text-soft); }
.grafico-lineas__leyenda span { display: inline-flex; align-items: center; gap: var(--sp1); }
.grafico-lineas__leyenda i { width: 10px; height: 10px; border-radius: var(--r-full); display: inline-block; }

/* Menú principal de Compras: separa los circuitos operativos para evitar una lista larga de botones. */
.compras-intro { border-left: 4px solid var(--brand); }
.compras-intro h2 { margin: var(--sp1) 0 var(--sp2); }
.compras-intro__eyebrow, .compras-menu__eyebrow { display: block; color: var(--brand-text); font-size: .78rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; }
.compras-menu { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--sp4); }
.compras-menu__grupo { display: flex; min-height: 210px; flex-direction: column; justify-content: space-between; gap: var(--sp4); background: var(--surface); border: 1px solid var(--border); border-radius: var(--r-lg); padding: var(--sp5); box-shadow: var(--sh1); }
.compras-menu__grupo h2 { margin: var(--sp1) 0 var(--sp2); font-size: 1.15rem; }
.compras-menu__grupo p { margin: 0; color: var(--text-soft); }
.compras-menu__grupo--principal { border-color: color-mix(in srgb, var(--brand) 35%, var(--border)); }
.compras-menu__acciones { display: flex; flex-wrap: wrap; gap: var(--sp2); align-items: center; }
@media (max-width: 767px) { .compras-menu { grid-template-columns: 1fr; gap: var(--sp3); } .compras-menu__grupo { min-height: 0; padding: var(--sp4); } .compras-menu__acciones .btn { flex: 1 1 auto; text-align: center; } }

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--sp2);
  min-height: var(--control-h); padding: 0 var(--sp4);
  border-radius: var(--r-sm); border: 0; font: inherit; font-weight: 600; font-size: .95rem;
  cursor: pointer; text-decoration: none; white-space: nowrap;
}
.btn svg { width: 20px; height: 20px; flex-shrink: 0; fill: none; stroke: currentColor; stroke-width: 1.8; }
.btn--pri, .btn--pri:visited { background: var(--brand); color: var(--on-brand); }
.btn--pri:hover { background: var(--brand-hover); }
.btn--sec, .btn--sec:visited { background: var(--surface-3); color: var(--text); }
.btn--sec:hover { background: var(--border); }
.btn--peligro, .btn--peligro:visited { background: var(--error-text); color: #fff; }
.btn--ok, .btn--ok:visited { background: var(--ok-bg); color: var(--ok-text); }
.btn--ok:hover { background: var(--ok-border); }
.btn--sm { min-height: 36px; padding: 0 var(--sp3); font-size: .85rem; }
.btn--google, .btn--google:visited { background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); }
.btn--google:hover { background: var(--surface-2); }

.alerta { display: flex; align-items: flex-start; gap: var(--sp3); padding: var(--sp3) var(--sp4); border-radius: var(--r-sm); border: 1px solid; margin-bottom: var(--sp4); }
.alerta__icono { width: 20px; height: 20px; flex-shrink: 0; margin-top: 1px; fill: none; stroke: currentColor; stroke-width: 1.8; }

/* Toasts: app.js promueve cada .alerta ya renderizada (flash de éxito/error
   del servidor, sea en la carga inicial de la página o inyectada por AJAX
   en un panel/drawer) a este contenedor fijo, en vez de dejarla como
   banner fijo arriba del contenido — que obligaba a scrollear para verla.
   El HTML/PHP no cambia: sigue siendo el mismo .alerta de siempre, esto
   solo lo reposiciona y le agrega entrada/salida animada. */
#toast-contenedor {
  position: fixed;
  top: var(--sp4);
  right: var(--sp4);
  z-index: 9998; /* debajo de .cargando-global (9999) a propósito: ese overlay ya cubre todo */
  display: flex;
  flex-direction: column;
  gap: var(--sp2);
  max-width: min(420px, calc(100vw - var(--sp4) * 2));
  pointer-events: none;
}
#toast-contenedor .alerta {
  margin-bottom: 0;
  box-shadow: 0 10px 30px rgba(2, 6, 23, .18);
  cursor: pointer;
  pointer-events: auto;
}
@media (prefers-reduced-motion: no-preference) {
  #toast-contenedor .alerta { animation: toast-entrar 220ms ease; }
  #toast-contenedor .alerta.toast-saliendo { animation: toast-salir 180ms ease forwards; }
  @keyframes toast-entrar { from { transform: translateX(120%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
  @keyframes toast-salir { to { transform: translateX(120%); opacity: 0; } }
}
@media (prefers-reduced-motion: reduce) {
  #toast-contenedor .alerta.toast-saliendo { opacity: 0; transition: opacity 120ms ease; }
}
@media (max-width: 480px) {
  #toast-contenedor { top: auto; bottom: calc(76px + env(safe-area-inset-bottom, 0px)); left: var(--sp3); right: var(--sp3); max-width: none; }
}
.alerta--ok    { background: var(--ok-bg);    color: var(--ok-text);    border-color: var(--ok-border); }
.alerta--error { background: var(--error-bg); color: var(--error-text); border-color: var(--error-border); }
.alerta--aviso { background: var(--warn-bg);  color: var(--warn-text);  border-color: var(--warn-border); }
.alerta--info  { background: var(--info-bg);  color: var(--info-text);  border-color: var(--info-border); }

.badge { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: var(--r-full); font-size: .78rem; font-weight: 600; }
.badge--ok      { background: var(--ok-bg);    color: var(--ok-text); }
.badge--neutro  { background: var(--surface-3); color: var(--text-soft); }
.badge--aviso   { background: var(--warn-bg);  color: var(--warn-text); }
.badge--peligro { background: var(--error-bg); color: var(--error-text); }
.badge--info    { background: var(--info-bg);  color: var(--info-text); }
.badge--licencia { background: var(--licencia-bg); color: var(--licencia-text); }

.lista-simple { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--sp2); }
.lista-simple li { display: flex; align-items: center; gap: var(--sp3); flex-wrap: wrap; }

.avatar { object-fit: cover; background: var(--surface-3); flex-shrink: 0; }
.avatar--sm { width: 40px; height: 40px; border-radius: var(--r-full); }
.avatar--md { width: 120px; height: 120px; border-radius: var(--r); }
.avatar--xl { width: 160px; height: 160px; border-radius: var(--r); }

.tabla-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.tabla { width: 100%; border-collapse: collapse; font-size: .95rem; }
.tabla th, .tabla td { padding: var(--sp3); text-align: left; border-bottom: 1px solid var(--border); }
.tabla thead th { background: var(--brand-soft); color: var(--brand-text); font-weight: 600; position: sticky; top: 0; }
.tabla tbody tr:hover { background: var(--surface-2); }
.tabla td.num { text-align: right; font-variant-numeric: tabular-nums; }

@media (min-width: 768px) and (max-width: 1023px) {
  .tabla { min-width: 640px; }
}

@media (max-width: 767px) {
  .tabla, .tabla tbody, .tabla tr, .tabla td { display: block; width: 100%; }
  .tabla thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .tabla tr {
    position: relative;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--r);
    padding: var(--sp3); margin-bottom: var(--sp3); box-shadow: var(--sh1);
  }
  .tabla td {
    border: 0; padding: var(--sp1) 0;
    display: grid; grid-template-columns: minmax(96px, 38%) 1fr; gap: var(--sp3); align-items: baseline;
  }
  .tabla td::before { content: attr(data-label); color: var(--text-soft); font-size: .8rem; text-transform: uppercase; letter-spacing: .02em; }
  .tabla td:empty { display: none; }
  .tabla td[colspan] { display: block; }
  .tabla td[colspan]::before { content: none; }
  .tabla td.celda-larga { grid-template-columns: 1fr; }
  .tabla td.celda-larga::before { display: block; margin-bottom: 2px; }
  .tabla td.celda-titulo::before { content: none; }
  .tabla td.celda-titulo { font-weight: 600; font-size: 1.02rem; }
  .tabla--lista tr { cursor: default; }
  .enlace-fila::after { content: ""; position: absolute; inset: 0; border-radius: var(--r); }
}

@media (min-width: 768px) {
  .tabla--lista tbody tr { cursor: pointer; }
  .fila-editable { cursor: pointer; }
}

.fila-resaltada > td { background: var(--warn-bg); box-shadow: inset 3px 0 0 var(--warn-border); }

.enlace-fila, .enlace-fila:visited { color: var(--text); text-decoration: none; font-weight: 600; }
.enlace-fila:hover, .enlace-fila:visited:hover { color: var(--brand-text); text-decoration: underline; }
/* min-width, no solo width: en table-layout:auto un `width` es apenas una
   sugerencia — con muchas columnas de texto compitiendo (ej. el listado
   de Empleados propios, 19 columnas), la columna de la foto se termina
   comprimiendo a 0px si no hay un piso duro. 64px = avatar--sm (40px) +
   el padding de celda a cada lado (--sp3 × 2 = 24px), si no el avatar
   redondo queda ovalado al no entrar completo. */
.celda-foto { width: 64px; min-width: 64px; }

/* Panel maestro-detalle (como AppSheet): listado + detalle lado a lado.
   Solo en escritorio (≥1024px) — en tablet/móvil no entra un split-pane
   legible, así que ahí se navega a página completa (ver app.js). */
tr.md-activa { background: var(--brand-soft) !important; }
.md-panel { display: none; }
.md-panel__header { display: flex; justify-content: space-between; align-items: center; gap: var(--sp3); margin-bottom: var(--sp4); flex-wrap: wrap; }
.md-panel__header h1 { margin: 0; }

/* Spinner + fundido genérico para cualquier contenedor en estado aria-busy
   (panel de detalle, drawer de formulario, hijos apilados, acordeón): un
   solo estilo cubre todos los fetch cortos de app.js sin tener que tocar
   cada función. El contenido nuevo siempre se pisa por innerHTML ANTES de
   que se saque aria-busy (ver app.js), así que esta transición de opacity
   duplica de arranque: atenúa el contenido viejo mientras "busy" está
   activo y funde hacia adentro el contenido nuevo apenas se saca el
   atributo — sin tocar cada call site en JS. */
[aria-busy="true"] {
  position: relative;
  opacity: .45;
  pointer-events: none;
  transition: opacity 160ms ease;
}
[aria-busy="true"]::after {
  content: '';
  position: absolute; top: var(--sp4); right: var(--sp4);
  width: 22px; height: 22px; border-radius: 50%;
  border: 3px solid var(--border-strong); border-top-color: var(--brand);
  animation: girar-carga 700ms linear infinite;
  z-index: 1;
}
@keyframes girar-carga { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  [aria-busy="true"]::after { animation: none; }
  [aria-busy="true"] { transition: none; }
}

/* Overlay global de "trabajando": para envíos de formulario normales (sin
   AJAX), como Sincronizar Excel, que hoy no muestran ningún aviso mientras
   procesan un archivo grande. */
.cargando-global {
  position: fixed; inset: 0; z-index: 9999;
  display: flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--bg) 65%, transparent);
  opacity: 0; visibility: hidden;
  transition: opacity 120ms ease;
}
.cargando-global--visible { opacity: 1; visibility: visible; }
.cargando-global__caja {
  display: flex; align-items: center; gap: var(--sp3);
  background: var(--surface); color: var(--text);
  border: 1px solid var(--border); border-radius: var(--r-lg);
  padding: var(--sp3) var(--sp5);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .2);
  font-weight: 600;
}
.cargando-global__spinner {
  width: 20px; height: 20px; border-radius: 50%; flex: none;
  border: 3px solid var(--border-strong); border-top-color: var(--brand);
  animation: girar-carga 700ms linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .cargando-global__spinner { animation: none; }
}

/* Barra fina arriba de la página: feedback inmediato al hacer clic en un
   link de navegación completa (menú lateral, paginación, "volver al
   listado"), donde antes no pasaba nada visible mientras el servidor
   armaba la respuesta. Nunca llega a completarse "sola": la página nueva
   reemplaza el DOM antes de que eso importe. */
.barra-carga {
  position: fixed; top: 0; left: 0; height: 3px; width: 0;
  background: var(--brand); z-index: 10000;
  opacity: 0; pointer-events: none;
  transition: width 400ms ease-out, opacity 150ms ease;
}
.barra-carga--visible { opacity: 1; width: 70%; }
@media (prefers-reduced-motion: reduce) {
  .barra-carga { transition: opacity 150ms ease; }
  .barra-carga--visible { width: 100%; }
}

/* La ficha de Personas se abre en un drawer derecho, igual que Editar. La
   lista queda detrás a ancho completo y cambiar de persona no la comprime. */
.drawer--detalle { width: min(920px, 94vw); overflow-y: auto; }
.drawer--detalle .drawer__panel { padding: var(--sp2) var(--sp5) var(--sp5); }
.drawer-detalle__cabecera { display: flex; justify-content: flex-end; position: sticky; top: calc(var(--sp2) * -1); z-index: 2; padding: var(--sp2) 0; background: var(--surface); }
.drawer-detalle__contenido { flex: 1; }
.drawer--detalle .md-panel { display: block; height: auto; min-height: 0; overflow: visible; }

/* Debajo de 1024px no hay split-pane (ver comentario arriba): el servidor
   igual arma la lista + el panel juntos para que un acceso directo a una
   ficha no pierda contexto, así que acá hay que elegir uno de los dos.
   Con un detalle activo se muestra SOLO el panel, a página completa (el
   equivalente táctil de "hacer clic en una fila"); sin eso, solo la lista. */
@media (max-width: 1023px) {
  .md-vista--activo .md-lista { display: none; }
  .md-vista--activo .md-panel { display: block; }
}

@media (min-width: 1024px) {
  /* La página no scrollea: el alto de .contenido queda atado al viewport
     (menos la topbar) para que la lista y el panel puedan repartirse ESE
     alto con su propio scroll interno, en vez de depender de position:sticky
     (que solo termina de "pegarse" después de scrollear la página, dejando
     el paginado fuera de la vista al entrar). */
  .contenido--fijo {
    height: calc(100vh - var(--topbar-h));
    height: calc(100dvh - var(--topbar-h));
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  .contenido--fijo .page-header { flex: 0 0 auto; }

  .md-vista {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp5);
    min-height: 0;
    flex: 1 1 auto;
    transition: grid-template-columns .2s ease;
  }
  .md-vista--activo { grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr); }
  .md-lista, .md-panel { height: 100%; min-height: 0; }
  .md-panel { display: none; overflow-y: auto; }
  .md-vista--activo .md-panel { display: block; }

  /* La lista no scrollea entera: el buscador y el paginado quedan siempre a
     la vista, solo el cuerpo de la tabla scrollea entre ambos. */
  .md-lista { display: flex; flex-direction: column; overflow: hidden; }
  .md-lista .tabla-wrap { flex: 1 1 auto; min-height: 0; overflow-y: auto; }
  .md-lista .buscador, .md-lista .pager { flex: 0 0 auto; }
  .md-panel .md-solo-pagina { display: none; }
}

.celda-accion { display: flex; gap: var(--sp2); flex-wrap: wrap; }
.celda-accion form { margin: 0; }

/* Select suelto dentro de una celda de tabla (ej. tipo de día por persona
   en Resumen Diario) — mismo aspecto que los .campo select de los
   formularios, sin tener que envolver la celda en un .campo entero. */
.celda-select form { margin: 0; }
.celda-select select {
  min-height: var(--control-h); padding: 0 var(--sp4) 0 var(--sp3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text); font: inherit; font-size: 14px;
}

.vacio { text-align: center; padding: var(--sp6) var(--sp3); color: var(--text-soft); }

.pager { display: flex; gap: var(--sp3); justify-content: center; align-items: center; flex-wrap: wrap; margin-top: var(--sp4); }
.pager a, .pager span { padding: var(--sp2) var(--sp3); border-radius: var(--r-sm); text-decoration: none; color: var(--brand-text); min-height: var(--control-h); display: inline-flex; align-items: center; }
.pager a { background: var(--brand-soft); }
.pager a:hover { background: var(--brand); color: var(--on-brand); }

/* Variante de arriba de la tabla: navegación de página, cantidad total y
   selector de filas en una sola fila, siempre visible sin depender de
   scrollear hasta el final de una lista de miles de filas. */
.pager--arriba {
  justify-content: space-between;
  margin-top: 0; margin-bottom: var(--sp3);
  padding-bottom: var(--sp3);
  border-bottom: 1px solid var(--border);
}
.pager--arriba .muted { margin: 0; }

.pager__nav { display: flex; gap: var(--sp3); align-items: center; }

.pager__tamanio { display: flex; align-items: center; gap: var(--sp2); margin-left: auto; color: var(--text-soft); font-size: .9rem; }
.pager__tamanio select { min-height: var(--control-h); padding: 0 var(--sp2); }

.orden-columna, .orden-columna:visited { color: inherit; text-decoration: none; white-space: nowrap; }
.orden-columna:hover, .orden-columna:visited:hover { color: var(--brand-text); text-decoration: underline; }

/* Filtros de Personas: los criterios más frecuentes siempre quedan a la
   vista; los de rango y CUIL se despliegan solo cuando hacen falta. */
.filtros-personas { margin-bottom: var(--sp4); }
.filtros-personas__cabecera { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp3); margin-bottom: var(--sp4); }
.filtros-personas__cabecera h2 { display: flex; align-items: center; gap: var(--sp2); margin: 0; font-size: 1.05rem; }
.filtros-personas__cabecera .ayuda { margin: var(--sp2) 0 0; }
.filtros-personas__contador { flex: 0 0 auto; padding: 4px var(--sp3); border-radius: var(--r-full); background: var(--brand-soft); color: var(--brand-text); font-size: .85rem; font-weight: 600; white-space: nowrap; }
.filtros-personas__form { display: block; }
.filtros-personas__tipos { display: flex; flex-wrap: wrap; gap: var(--sp2) var(--sp5); margin-top: var(--sp3); }
.filtros-personas__tipos .switch-fila { min-height: auto; }
.filtros-personas__avanzados { margin-top: var(--sp4); border-top: 1px solid var(--border); padding-top: var(--sp3); }
.filtros-personas__avanzados summary { cursor: pointer; color: var(--brand-text); font-weight: 600; width: fit-content; }
.filtros-personas__avanzados-grid { margin-top: var(--sp4); }

@media (min-width: 1100px) {
  .form-grid.filtros-personas__principales { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .form-grid.filtros-personas__avanzados-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
  .md-vista--activo .form-grid.filtros-personas__principales { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 480px) {
  .filtros-personas__cabecera { flex-direction: column; }
}

.buscador { display: flex; gap: var(--sp2); margin-bottom: var(--sp4); flex-wrap: wrap; }
.buscador input[type=search] { flex: 1; min-width: 200px; }

/* Pestañas de filtro simple (ej. situación del legajo) arriba de un listado. */
.pestanias { display: flex; gap: var(--sp2); flex-wrap: wrap; margin-bottom: var(--sp4); }
.pestania, .pestania:visited {
  display: inline-flex; align-items: center; min-height: 34px; padding: 0 var(--sp3);
  border-radius: var(--r-full); background: var(--surface-2); color: var(--text-soft);
  text-decoration: none; font-size: .85rem; font-weight: 600;
}
.pestania:hover { background: var(--surface-3); }
.pestania--activa, .pestania--activa:visited { background: var(--brand-soft); color: var(--brand-text); }

.datos { display: grid; grid-template-columns: 1fr; gap: 0 var(--sp5); margin: 0; }
.datos dt { color: var(--text-soft); font-size: .85rem; margin-top: var(--sp3); }
.datos dd { margin: 2px 0 0; font-size: 1.02rem; }
@media (min-width: 768px) {
  .datos { grid-template-columns: 1fr 1fr; }
}

.form-grid { display: grid; gap: var(--sp4); grid-template-columns: 1fr; }
@media (min-width: 768px) {
  .form-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .campo--full { grid-column: 1 / -1; }
}
/* Subgrupo de campos cortos y relacionados (ej. fecha + horarios) que
   necesitan ir juntos en una fila propia, sin heredar el ancho de las
   2 columnas del form-grid que los rodea. */
.campo-grupo { display: grid; gap: var(--sp4); grid-template-columns: 1fr; }
@media (min-width: 600px) {
  .campo-grupo--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.campo label { display: block; font-weight: 600; margin-bottom: var(--sp2); font-size: .9rem; }
/* .campo label (arriba) le gana en especificidad a .switch-fila { display: flex }
   cuando el switch vive dentro de un .campo, dejando el interruptor
   desalineado con su etiqueta (cae al flujo normal en vez de centrarse). */
.campo .switch-fila { display: flex; font-weight: normal; margin-bottom: 0; }
.campo input, .campo select, .campo textarea {
  width: 100%; min-height: var(--control-h); padding: 0 var(--sp3);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface); color: var(--text); font: inherit; font-size: 16px;
}
.campo select { padding-right: var(--sp4); }

/* Selector con filtro de texto (selector_buscable() en PHP, ver app.js
   inicializarSelectoresBuscables()). El <select> real queda oculto adentro
   y sigue siendo la fuente de verdad; esto es solo la capa visual. */
.selector-buscable { position: relative; }
.selector-buscable__control {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp1);
  min-height: var(--control-h); padding: 4px var(--sp2);
  border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  background: var(--surface);
}
.selector-buscable__control:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-soft); }
.selector-buscable__chips { display: flex; flex-wrap: wrap; gap: var(--sp1); }
.selector-buscable__input {
  flex: 1 1 80px; min-width: 80px; min-height: 34px; border: 0; padding: 0 var(--sp1);
  background: transparent; color: var(--text); font: inherit; font-size: 16px; outline: none;
}
.selector-buscable__lista {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 20;
  max-height: 260px; overflow-y: auto; margin: 0; padding: var(--sp1) 0; list-style: none;
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--r-sm);
  box-shadow: var(--sh1);
}
.selector-buscable__opcion, .selector-buscable__vacio { padding: var(--sp2) var(--sp3); font-size: .92rem; cursor: pointer; }
.selector-buscable__vacio { color: var(--text-soft); cursor: default; }
.selector-buscable__opcion:hover, .selector-buscable__opcion--resaltada { background: var(--surface-2); }
.selector-buscable__opcion--activa { color: var(--brand-text); font-weight: 600; }

.chip-quitable {
  display: inline-flex; align-items: center; gap: 4px; padding: 3px 4px 3px var(--sp2);
  border-radius: var(--r-full); background: var(--brand-soft); color: var(--brand-text);
  font-size: .82rem; font-weight: 600; white-space: nowrap;
}
.chip-quitable__quitar {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; border-radius: 50%; border: 0; background: transparent;
  color: inherit; font-size: 1rem; line-height: 1; cursor: pointer;
}
.chip-quitable__quitar:hover { background: rgba(0, 0, 0, .12); }
.campo .ayuda { color: var(--text-soft); font-size: .85rem; margin-top: var(--sp2); }

/* Validación de formularios (app.js: inicializarValidacionFormularios()) —
   mensaje propio en vez del globo nativo del navegador, que además no se ve
   para el <select> escondido de selector_buscable(). */
.campo input.campo-invalido, .campo select.campo-invalido, .campo textarea.campo-invalido { border-color: var(--error-border); }
.campo-error-msg { display: block; color: var(--error-text); font-size: .85rem; margin-top: var(--sp2); }
.selector-buscable:has(select.campo-invalido) .selector-buscable__control { border-color: var(--error-border); }
abbr.req { color: var(--error-text); text-decoration: none; margin-left: 2px; }
/* Tabs (ej. ficha de Persona: Datos de la persona / Antecedentes de salud /
   Historial de empleo) — agrupa varias .card que antes iban todas seguidas
   en una sola columna larga. Ver app.js: inicializarTabs(). */
.tabs__botones { display: flex; gap: var(--sp2); border-bottom: 1px solid var(--border); margin-bottom: var(--sp5); flex-wrap: wrap; overflow-x: auto; }
.tabs__boton {
  padding: var(--sp3) var(--sp4); border: 0; background: transparent; color: var(--text-soft);
  font: inherit; font-weight: 600; font-size: .92rem; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px; white-space: nowrap;
}
.tabs__boton:hover { color: var(--text); }
.tabs__boton[aria-selected="true"] { color: var(--brand-text); border-bottom-color: var(--brand); }
.tabs__boton:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.form-acciones { display: flex; justify-content: flex-end; gap: var(--sp3); flex-wrap: wrap; margin-top: var(--sp5); padding-top: var(--sp4); border-top: 1px solid var(--border); }
@media (max-width: 767px) { .form-acciones .btn { flex: 1 1 100%; } }
.seccion-desplegable { margin-top: var(--sp4); padding: var(--sp4); border: 1px solid var(--border); border-radius: var(--r); background: var(--surface-2); }
.seccion-desplegable summary { cursor: pointer; color: var(--primary); font-weight: 700; }
.seccion-desplegable > p { margin: var(--sp3) 0 0; }

.campo-archivo { display: flex; align-items: center; gap: var(--sp3); flex-wrap: wrap; }
.campo-archivo .input-archivo { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.campo-archivo .input-archivo:focus-visible + label { box-shadow: var(--ring); }
.archivo-nombre { color: var(--text-soft); font-size: .9rem; }

.checkbox-fila { display: flex; align-items: center; gap: var(--sp2); min-height: var(--control-h); }
.checkbox-fila input { width: auto; min-height: auto; }

.opciones-en-linea { display: flex; flex-direction: column; gap: var(--sp2); }
.opciones-en-linea .checkbox-fila,
.opciones-en-linea .switch-fila { min-height: auto; }

/* ---------- Interruptor (toggle switch) ---------- */
.switch-fila { display: flex; align-items: center; gap: var(--sp3); min-height: var(--control-h); }
.switch {
  position: relative; display: inline-block; flex: none;
  width: 40px; height: 24px;
}
.switch input {
  position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; margin: 0;
}
.switch__pista {
  position: absolute; inset: 0; border-radius: var(--r-full);
  background: var(--border-strong); cursor: pointer;
  transition: background-color 150ms ease;
}
.switch__pista::before {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: 50%;
  background: var(--surface); box-shadow: 0 1px 2px rgba(0,0,0,.3);
  transition: transform 150ms ease;
}
.switch input:checked + .switch__pista { background: var(--brand); }
.switch input:checked + .switch__pista::before { transform: translateX(16px); }
.switch input:focus-visible + .switch__pista { box-shadow: var(--ring); }
.switch input:disabled + .switch__pista { opacity: .5; cursor: not-allowed; }
.switch-fila__etiqueta { font-weight: 600; }

/* ---------- 5. Utilidades ---------- */
.muted { color: var(--text-soft); }
.nowrap { white-space: nowrap; }
.solo-movil { display: none; }
.oculto-movil { display: inline; }
/* Doble clase a propósito: ".pager span" (ver arriba, display:inline-flex)
   tiene más especificidad que una sola clase y pisaba estas dos reglas,
   mostrando "de" y "/" juntos en el paginador. */
.pager .solo-movil { display: none; }
.pager .oculto-movil { display: inline; }
@media (max-width: 767px) {
  .solo-movil, .pager .solo-movil { display: inline; }
  .oculto-movil, .pager .oculto-movil { display: none; }
}
.visualmente-oculto {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}
