/* ============================================================
   Preinforme Analytics — sistema de diseño (Fase 1)
   Tokens claro/oscuro · Inter · acento teal clínico · Lucide
   ============================================================ */

:root {
  /* Acento clínico (teal/cyan) */
  --accent: #0e7490;
  --accent-2: #0891b2;
  --accent-soft: #ecfeff;
  --accent-ink: #155e75;
  /* Tinta SOBRE el acento. En claro el acento es teal oscuro y admite blanco
     (5,5:1); en oscuro es cian brillante y el blanco cae a 1,8:1 — ilegible.
     Este token evita tener que acordarse caso por caso. */
  --on-accent: #ffffff;
  --danger-on: #ffffff;   /* 4,83:1 sobre el rojo claro */

  /* Borde de CONTROLES (campos, botones secundarios). --line está pensado para
     separadores sutiles (1,2:1) y no llega al 3:1 que WCAG 1.4.11 exige para
     que se distinga el límite de un control interactivo. */
  --field-border: #7d8fa6;   /* 3,31:1 sobre blanco */
  /* Texto de marcador de posición: --muted al 70% quedaba en 2,7:1. */
  --placeholder: #64748b;    /* 4,76:1 sobre blanco */

  /* Superficies y tinta (escala slate) */
  --bg: #f4f6f8;
  --card: #ffffff;
  --surface-2: #f8fafc;
  --ink: #0f172a;
  --muted: #64748b;
  --line: #e2e8f0;

  /* Semánticos */
  --ok: #16a34a;       --ok-bg: #dcfce7;      --ok-ink: #166534;
  --warn: #d97706;     --warn-bg: #fef3c7;    --warn-ink: #92400e;
  --danger: #dc2626;   --danger-bg: #fee2e2;  --danger-ink: #991b1b;
  --info: #0284c7;     --info-bg: #e0f2fe;    --info-ink: #075985;

  /* Resaltados suaves (paneles comparativos, citas) */
  --hl-warn: #fff7ed;
  --hl-ok: #f0fdf4;
  --hl-danger: #fef2f2;
  --hl-info: #eff6ff;

  /* Alias de compatibilidad: varias vistas usaban estos nombres SIN definirlos
     (el borde entonces no se dibujaba y los fondos quedaban blancos en oscuro). */
  --border: var(--line);
  --surface: var(--card);
  --bg-soft: var(--surface-2);
  --hl: var(--surface-2);

  /* Verde-lima para la banda "Destacado/Bueno" (badge semántico) */
  --good: #4d7c0f;     --good-bg: #ecfccb;    --good-ink: #3f6212;

  /* Escala de espaciado (múltiplos de 4) — ritmo vertical consistente */
  --s1: 4px; --s2: 8px; --s3: 12px; --s4: 16px; --s5: 24px; --s6: 32px;

  /* Escala tipográfica (7 pasos, piso duro en 11px) */
  --fs-3xs: 11px; --fs-2xs: 12px; --fs-xs: 13px; --fs-sm: 14px;
  --fs-md: 16px; --fs-lg: 20px; --fs-xl: 27px; --fs-2xl: 40px;

  /* Sidebar */
  --sidebar-bg: #0f172a;
  --sidebar-ink: #cbd5e1;
  --sidebar-muted: #94a3b8;   /* 6,96:1 (antes #64748b, 3,75:1) */
  --sidebar-hover: #1e293b;
  --sidebar-active: #155e75;

  /* Forma y elevación */
  --radius: 12px;
  --radius-sm: 8px;
  --shadow: 0 1px 2px rgba(15, 23, 42, 0.05), 0 1px 3px rgba(15, 23, 42, 0.06);
  --shadow-lg: 0 10px 30px rgba(15, 23, 42, 0.12);
  --focus: 0 0 0 3px rgba(14, 116, 144, 0.25);

  /* Movimiento */
  --t-fast: 150ms ease;
  --t-med: 220ms ease;

  /* Compat: vistas antiguas usan var(--brand,…) */
  --brand: var(--accent);
}

[data-theme='dark'] {
  --accent: #22d3ee;
  --accent-2: #06b6d4;
  --accent-soft: #083344;
  --accent-ink: #67e8f9;
  /* Sobre cian brillante va tinta oscura (9,4:1), no blanco (1,8:1). */
  --on-accent: #06202b;
  --danger-on: #3b0a0a;   /* el rojo oscuro es CLARO: tinta oscura (6,15:1) */
  --field-border: #5d719a;   /* 3,55:1 sobre la tarjeta oscura */
  --placeholder: #93a7c0;    /* 7,06:1 sobre la tarjeta oscura */

  --bg: #0b1220;
  --card: #111a2c;
  --surface-2: #16213a;
  --ink: #e2e8f0;
  --muted: #8aa0b8;
  --line: #243349;

  --ok: #4ade80;       --ok-bg: #14532d;      --ok-ink: #bbf7d0;
  --warn: #fbbf24;     --warn-bg: #57350b;    --warn-ink: #fde68a;
  --danger: #f87171;   --danger-bg: #5f1d1d;  --danger-ink: #fecaca;
  --info: #38bdf8;     --info-bg: #0c4a6e;    --info-ink: #bae6fd;

  --hl-warn: #2a2113;
  --hl-ok: #102418;
  --hl-danger: #2a1414;
  --hl-info: #0f2033;

  --border: var(--line);
  --surface: var(--card);
  --bg-soft: var(--surface-2);
  --hl: var(--surface-2);

  --good: #a3e635;     --good-bg: #1a2e05;    --good-ink: #d9f99d;

  --sidebar-bg: #070d1a;
  --sidebar-ink: #b6c2d4;
  --sidebar-muted: #94a3b8;   /* 7,57:1 (antes #5b6b80, 3,57:1) */
  --sidebar-hover: #131e33;
  --sidebar-active: #0e4453;

  --shadow: 0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.55);
  --focus: 0 0 0 3px rgba(34, 211, 238, 0.3);
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  background: var(--bg);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
  /* Render nítido y consistente en macOS (evita el texto "grueso/borroso"
     del subpixel-antialiasing por defecto). Práctica estándar en SaaS. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-feature-settings: 'cv11', 'ss01';
  transition: background var(--t-med), color var(--t-med);
}
/* --accent (#0e7490) da 5,4:1 sobre blanco; --accent-2 quedaba en 3,7:1 (falla
   AA). En oscuro --accent (#22d3ee) ya tiene contraste de sobra. */
a { color: var(--accent); text-decoration: none; }
[data-theme='dark'] a { color: var(--accent); }
a:hover { text-decoration: underline; }
/* outline (no box-shadow) respeta el radio del elemento y NO lo recorta el
   overflow de .table-scroll ni de .cmdk; se mantiene el glow como refuerzo. */
:focus-visible { outline: 2px solid var(--accent-2); outline-offset: 2px; box-shadow: var(--focus); }
::selection { background: var(--accent); color: var(--on-accent); }

/* Scrollbars discretos */
* { scrollbar-width: thin; scrollbar-color: var(--line) transparent; }
*::-webkit-scrollbar { width: 9px; height: 9px; }
*::-webkit-scrollbar-thumb { background: var(--line); border-radius: 8px; }
*::-webkit-scrollbar-track { background: transparent; }

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

/* ── Íconos (Lucide) ─────────────────────────────────────── */
.icon, svg.lucide { width: 17px; height: 17px; stroke-width: 2; flex-shrink: 0; vertical-align: -3px; }
.nav svg.lucide { width: 17px; height: 17px; opacity: 0.85; }
.btn svg.lucide { width: 15px; height: 15px; vertical-align: -2px; }
/* Ícono que abre un título de sección: en color de acento, como Linear */
h2 > svg.lucide:first-child, h3 > svg.lucide:first-child { color: var(--accent); width: 16px; height: 16px; margin-right: 2px; }

/* ── Sidebar ─────────────────────────────────────────────── */
.sidebar {
  width: 232px; background: var(--sidebar-bg); color: var(--sidebar-ink); flex-shrink: 0;
  display: flex; flex-direction: column; position: sticky; top: 0;
  /* 100vh en un móvil es la altura GRANDE del viewport: incluye la franja que
     ocupa la barra de direcciones del navegador. La barra lateral quedaba más
     alta que la pantalla y su pie —el bloque de usuario con «Salir»— caía
     fuera de la vista, sobre todo con el menú largo del administrador.
     100dvh es la altura realmente visible en cada momento. */
  height: 100vh;
  height: 100dvh;
  transition: transform var(--t-med);
}
/* La marca y el pie no se encogen: lo que cede es la lista de enlaces, que ya
   tiene su propio scroll. Así «Salir» siempre está a la vista. */
.sidebar .brand, .sidebar .userbox { flex-shrink: 0; }
.brand { padding: 16px 14px 14px; display: flex; align-items: center; gap: 10px; border-bottom: 1px solid rgba(148, 163, 184, 0.1); }
.brand .logo, .login-brand .logo {
  width: 34px; height: 34px; border-radius: 9px; flex-shrink: 0;
  background: linear-gradient(135deg, #0891b2, #155e75);
  display: inline-flex; align-items: center; justify-content: center; color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.18);
}
.brand .logo svg, .login-brand .logo svg { width: 19px; height: 19px; }
.brand-text { font-weight: 700; font-size: 13.5px; letter-spacing: -0.01em; color: #f1f5f9; line-height: 1.25; white-space: nowrap; }
.brand-text small { display: block; color: var(--sidebar-muted); font-weight: 500; font-size: var(--fs-3xs); letter-spacing: .4px; text-transform: uppercase; }
.login-brand { display: flex; justify-content: center; margin-bottom: 12px; }
.login-brand .logo { width: 44px; height: 44px; border-radius: 12px; }
.login-brand .logo svg { width: 24px; height: 24px; }
.login-card h1 { text-align: center; }
.login-card > .muted { text-align: center; }
.nav { padding: 6px 10px; overflow-y: auto; flex: 1; }
/* El menú del admin son 34 enlaces y a 1280×900 caben 16: el resto vivía tras
   un scroll SIN NINGUNA SEÑAL, y el último elemento visible era el rótulo
   «CALIDAD DEL DATO» sin un solo ítem debajo — un encabezado huérfano se lee
   como una sección que no cargó, y el jefe de programa no sabe que existe la
   mitad de su producto. Misma máscara de desbordamiento que .table-scroll,
   en vertical; las clases las pone PA.marcaMenu según el scroll real. */
.nav.hay-mas {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px, #000 calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 24px, #000 calc(100% - 28px), transparent 100%);
}
.nav.hay-mas.arriba {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
}
.nav.hay-mas.abajo {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
  mask-image: linear-gradient(to bottom, transparent 0, #000 24px);
}
.nav.hay-mas.arriba.abajo { -webkit-mask-image: none; mask-image: none; }
.nav a {
  display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: var(--radius-sm);
  color: var(--sidebar-ink); margin-bottom: 1px; font-weight: 500; font-size: 13px;
  border-left: 2px solid transparent; transition: background var(--t-fast), color var(--t-fast);
}
.nav a:hover { background: var(--sidebar-hover); text-decoration: none; color: #fff; }
.nav a.active { background: var(--sidebar-active); color: #fff; border-left-color: var(--accent); }
.nav .nav-sec { color: var(--sidebar-muted); font-size: var(--fs-3xs); text-transform: uppercase; padding: 14px 10px 4px; letter-spacing: .8px; font-weight: 600; }
/* Secciones plegables: el menú del admin son 34 enlaces y a 1280×900 caben 16.
   Plegadas, cabe todo y la estructura del producto se ve de una vez; abierta
   va solo la sección de la página actual (lo decide el servidor, ver top.ejs,
   para que no haya parpadeo). El rótulo se comporta como el rótulo de sección
   que era, más el triángulo y el número de ítems. */
.nav-grupo > summary {
  color: var(--sidebar-muted); font-size: var(--fs-3xs); text-transform: uppercase;
  padding: 12px 10px 5px; letter-spacing: .8px; font-weight: 600;
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; gap: 6px;
}
.nav-grupo > summary::-webkit-details-marker { display: none; }
/* Triángulo propio: el nativo no se puede colorear ni alinear. */
.nav-grupo > summary::before {
  content: ''; flex: none; width: 0; height: 0;
  border-left: 4px solid currentColor; border-top: 3.5px solid transparent; border-bottom: 3.5px solid transparent;
  transition: transform var(--t-fast); transform-origin: 25% 50%;
}
.nav-grupo[open] > summary::before { transform: rotate(90deg); }
.nav-grupo > summary:hover { color: var(--sidebar-ink); }
.nav-grupo-n { margin-left: auto; opacity: .55; font-variant-numeric: tabular-nums; letter-spacing: 0; }
/* Sección plegada A MANO que contiene la página actual: el menú tiene que
   seguir diciendo dónde estás aunque el enlace activo no se vea. No se
   reabre —plegarla fue una decisión del usuario—, se señala. */
.nav-grupo.tiene-activa > summary { color: var(--sidebar-ink); }
.nav-grupo.tiene-activa > summary .nav-grupo-n::after {
  content: ''; display: inline-block; vertical-align: middle;
  width: 6px; height: 6px; margin-left: 6px; border-radius: 50%;
  background: var(--accent);
}
/* Foco visible: el summary es el control con el que se navega por teclado. */
.nav-grupo > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; border-radius: var(--radius-sm); }
.sidebar .userbox { padding: 12px 16px; border-top: 1px solid rgba(148, 163, 184, 0.12); font-size: 12px; color: var(--sidebar-muted); }
.sidebar .userbox b { color: var(--sidebar-ink); display: block; }

/* Drawer móvil */
.nav-toggle { display: none; background: none; border: none; color: var(--ink); cursor: pointer; padding: 6px; border-radius: 8px; }
.nav-toggle:hover { background: var(--surface-2); }
.backdrop { display: none; }
@media (max-width: 768px) {
  .sidebar { position: fixed; left: 0; top: 0; z-index: 60; transform: translateX(-100%); box-shadow: var(--shadow-lg); }
  .sidebar.open { transform: translateX(0); }
  .nav-toggle { display: inline-flex; }
  .backdrop.show { display: block; position: fixed; inset: 0; background: rgba(2, 6, 23, 0.5); z-index: 50; }
}

/* ── Contenido / topbar ──────────────────────────────────── */
.content { flex: 1; min-width: 0; }
.topbar {
  background: var(--card);
  background: color-mix(in srgb, var(--card) 85%, transparent); backdrop-filter: blur(8px);
  border-bottom: 1px solid var(--line); padding: 12px 28px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  position: sticky; top: 0; z-index: 10;
}
.topbar h1 { font-size: 17px; margin: 0; font-weight: 600; letter-spacing: -0.01em; display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Agrupa el botón de menú (que ya no vive dentro del h1) con el título, para
   que la barra siga siendo «título a la izquierda · acciones a la derecha». */
.topbar-left { display: flex; align-items: center; gap: 10px; min-width: 0; }
/* Saltar al contenido: oculto hasta recibir el foco con Tab (WCAG 2.4.1). */
.skip-link {
  position: absolute; left: 8px; top: -60px; z-index: 100;
  background: var(--accent); color: var(--on-accent); padding: 10px 16px; border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 600; text-decoration: none; transition: top .15s ease;
}
.skip-link:focus { top: 8px; color: var(--on-accent); }
/* El destino del salto recibe foco por programa: sin contorno propio. */
#contenido:focus { outline: none; }
/* La topbar es sticky: sin esto tapa el elemento recién enfocado al tabular. */
html { scroll-padding-top: 72px; }
/* El destino de un salto de ancla (p. ej. «Esperan mi firma» → #cola)
   destella UNA vez al llegar: sin confirmación visible, cuando la tarjeta ya
   estaba a la vista el click parecía no hacer nada. */
.card:target { animation: ancla-flash 1.6s ease-out 1; }
@keyframes ancla-flash {
  0%, 55% { box-shadow: 0 0 0 2px var(--accent); }
  100% { box-shadow: 0 0 0 2px transparent; }
}
@media (prefers-reduced-motion: reduce) { .card:target { animation: none; } }

/* ── Avisos (toasts) ──────────────────────────────────────
   Antes se creaban con la paleta CLARA quemada en un style inline (verde/rojo
   sólido con texto blanco), que en tema oscuro desentonaba y en varios casos
   no llegaba al contraste. Ahora usan los pares -bg/-ink, pensados justamente
   para texto sobre fondo teñido en ambos temas, y se apilan en una región viva. */
.pa-toasts {
  position: fixed; bottom: 20px; right: 20px; z-index: 9999;
  display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
  pointer-events: none; max-width: min(380px, calc(100vw - 32px));
}
.pa-toast {
  pointer-events: auto;
  padding: 11px 16px; border-radius: var(--radius-sm);
  font-size: 13.5px; font-weight: 600; line-height: 1.4;
  box-shadow: var(--shadow-lg, 0 10px 30px rgba(0,0,0,.18));
  border: 1px solid var(--line); border-left-width: 4px;
  background: var(--card); color: var(--ink);
  animation: pa-toast-in .18s ease-out;
}
.pa-toast.is-ok    { background: var(--ok-bg);     color: var(--ok-ink);     border-left-color: var(--ok); }
.pa-toast.is-warn  { background: var(--warn-bg);   color: var(--warn-ink);   border-left-color: var(--warn); }
.pa-toast.is-error { background: var(--danger-bg); color: var(--danger-ink); border-left-color: var(--danger); }
@keyframes pa-toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@media (max-width: 640px) {
  .pa-toasts { left: 16px; right: 16px; bottom: 16px; align-items: stretch; max-width: none; }
}
@media (prefers-reduced-motion: reduce) { .pa-toast { animation: none; } }
/* Centrado y con un ancho útil mayor: en monitores grandes el contenido usa
   más espacio (menos scroll horizontal en tablas anchas) y no se pega a la
   izquierda; en pantallas medianas/pequeñas se ajusta el padding. */
.container { padding: 24px 28px; max-width: 1440px; margin: 0 auto; width: 100%; }
/* Monitores muy anchos: aprovechar el espacio sin que las líneas se estiren
   en exceso en tablas y formularios. */
@media (min-width: 1700px) { .container { max-width: 1600px; } }
@media (max-width: 1000px) { .container { padding: 20px 20px; } }
@media (max-width: 768px)  { .container { padding: 16px 14px; } }
@media (max-width: 480px)  { .container { padding: 14px 11px; } }

/* ── Responsive: topbar compacta en móvil ────────────────── */
@media (max-width: 768px) {
  .topbar { padding: 10px 14px; gap: 8px; flex-wrap: nowrap; }
  .topbar h1 { font-size: 16px; min-width: 0; }
  /* el título puede truncar; nunca empuja la barra de acciones */
  .topbar-title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .topbar .toolbar { gap: 6px; flex-wrap: nowrap; flex-shrink: 0; }
  /* botones de la topbar = solo ícono (el texto queda como title/tooltip) */
  .topbar .toolbar .btn, .topbar .search-btn { padding: 8px; }
  .topbar .toolbar .btn-label, .topbar .search-btn .btn-label, .search-btn .kbd { display: none; }
  .topbar .toolbar .btn svg, .topbar .search-btn svg { vertical-align: middle; }
}

.theme-toggle { background: none; border: 1px solid var(--line); color: var(--muted); width: 34px; height: 34px; border-radius: 50%; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: all var(--t-fast); }
.theme-toggle:hover { color: var(--ink); border-color: var(--muted); }

/* ── Cards & grid ────────────────────────────────────────── */
.grid { display: grid; gap: var(--s4); margin-bottom: var(--s4); }
/* Una tarjeta DENTRO de una rejilla no lleva margen propio: lo pone el `gap`.
   Antes cada llamada lo apagaba a mano con style="margin:0" (21 veces) y las
   que se olvidaban dejaban una fila más separada que la de al lado. */
.grid > .card, .grid > .grid { margin-bottom: 0; }
/* Rejilla como último hijo de una tarjeta: su margen sobraría por dentro. */
.card > .grid:last-child { margin-bottom: 0; }
/* Los items de grid traen min-width:auto: sin esto, una tabla/canvas ancho
   estira la columna (y rompe el scroll-horizontal contenido en móvil). */
.grid > * { min-width: 0; }
.grid.cols-5 { grid-template-columns: repeat(5, 1fr); }
.grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
@media (max-width: 1000px) { .grid.cols-5, .grid.cols-4, .grid.cols-3, .grid.cols-2 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 640px) { .grid.cols-5, .grid.cols-4, .grid.cols-3, .grid.cols-2 { grid-template-columns: 1fr; } }

/* ── Tablas: scroll horizontal contenido en móvil ────────── */
/* PA.wrapTables() envuelve cada <table> en .table-scroll para que las tablas
   anchas se desplacen dentro de su tarjeta en vez de romper el layout. */
/* Celda con varios elementos apilables (N° de acceso, botones del visor): se
   acotan y envuelven en vez de crecer en una sola línea. Sin esto, un estudio
   con tres números de acceso estiraba la tabla entera a lo ancho. */
.cell-acceso { display: flex; flex-wrap: wrap; gap: 3px; max-width: 132px; }
.cell-acceso > * { max-width: 100%; }
/* Varios N° de acceso (o varios botones del visor): solo se muestra el primero,
   con un «+N» que despliega el resto. Apilarlos todos inflaba la fila a más del
   doble de alto que las demás; así la tabla mantiene su ritmo y ningún dato
   queda oculto. Los elementos plegables van en .acc-items para que el propio
   botón «+N» no cuente en la numeración. */
.acc-chips { display: flex; flex-wrap: wrap; gap: 3px; align-items: center; min-width: 0; }
.acc-items { display: inline-flex; flex-wrap: wrap; gap: 3px; align-items: center; min-width: 0; }
.acc-items > *:nth-child(n + 2) { display: none; }
.acc-chips.abierto .acc-items > * { display: inline-flex; }
.acc-mas {
  border: 1px solid var(--field-border); background: var(--surface-2); color: var(--muted);
  border-radius: 999px; padding: 1px 7px; font-size: var(--fs-3xs); font-weight: 700;
  cursor: pointer; line-height: 1.5; position: relative;
}
.acc-mas::after { content: ''; position: absolute; inset: -7px; }  /* área táctil */
.acc-mas:hover { color: var(--accent); border-color: var(--accent); }
/* La clave del paciente es un identificador largo: se recorta y el valor
   completo queda en el title (y sigue siendo copiable desde el detalle). */
.clave-paciente {
  font-size: var(--fs-3xs); flex-basis: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; max-width: 100%; padding: 2px; position: relative; }
/* Cuando la tabla CABE, el envoltorio deja de recortar. No es cosmética: con
   `overflow-x: auto` el navegador computa `overflow-y: auto`, el envoltorio
   pasa a ser el contenedor de referencia de `position: sticky` y, como nunca
   se desplaza en vertical, la cabecera de la tabla no se quedaba pegada a
   nada. La clase la pone marcaDesborde() midiendo, y se recalcula al cambiar
   el tamaño. */
.table-scroll.cabe { overflow: visible; }
/* Cuando la tabla sigue hacia el costado, su borde se desvanece: indica que
   hay más columnas y que se puede arrastrar. Sin esto, una columna cortada en
   el filo de la tarjeta se lee como un error de maquetación. Se hace con
   máscara sobre el propio contenedor —no con pseudo-elementos flotantes, que
   dentro de un contenedor con scroll se descolocan— y las clases las pone
   PA.wrapTables según la posición real del scroll. */
.table-scroll.is-scrollable {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 34px, #000 calc(100% - 34px), transparent 100%);
}
.table-scroll.is-scrollable.at-start {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
  mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent 100%);
}
.table-scroll.is-scrollable.at-end {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 34px);
  mask-image: linear-gradient(to right, transparent 0, #000 34px);
}
/* En los extremos a la vez (cabe todo) no hay nada que insinuar. */
.table-scroll.is-scrollable.at-start.at-end { -webkit-mask-image: none; mask-image: none; }
/* Altura tope opcional (clase adicional) para que el thead pegajoso tenga
   contra qué pegarse en tablas muy largas. */
.table-scroll.tall { max-height: 72vh; overflow-y: auto; }
/* En pantallas medianas y móviles compactamos las celdas para que quepan más
   columnas antes de necesitar scroll horizontal; el min-width solo se aplica
   en móvil real para no romper el layout de tablas muy anchas. */
@media (max-width: 1000px) {
  th, td { padding: 7px 8px; }
}
@media (max-width: 768px) {
  .table-scroll table { min-width: 520px; }
  .table-scroll { margin: 0 -2px; }
  table { font-size: 12.5px; }
  th, td { padding: 6px 7px; }
}

/* Formularios y barras de acción apilan en móvil.
   !important a propósito (U11): varios filtros llevan min-width inline
   (urgentes, buscar, mensajes, discrepancias) que ganaba a esta regla y
   dejaba los campos angostos con espacio muerto en el celular. */
@media (max-width: 640px) {
  .row { gap: 8px; }
  .row > * { min-width: 100% !important; flex-basis: 100% !important; }
  .toolbar { gap: 8px; }
  .toolbar .btn, .toolbar .btn.secondary, .toolbar a.btn { flex: 1 1 auto; justify-content: center; }
}
.card {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 18px; margin-bottom: var(--s4);
  transition: background var(--t-med), border-color var(--t-med);
}
.card h2, .card h3 { margin: 0 0 12px; font-size: 14.5px; font-weight: 600; letter-spacing: -0.01em; }
/* Banner de aviso: tarjeta con borde de color a la izquierda (callout). */
.card--accent { border-left: 4px solid var(--accent); }
.card--warn { border-left: 4px solid var(--warn); }
.card--ok { border-left: 4px solid var(--ok); }
.card--danger { border-left: 4px solid var(--danger); }
/* Cabecera de tarjeta: título/subtítulo a la izquierda, acciones a la derecha. */
.card-head { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: var(--s3); }
.stack > * + * { margin-top: var(--s3); }

.kpi { display: flex; flex-direction: column; gap: 4px; }
/* line-height explícito: sin él hereda el 1.5 del body y la reserva de dos
   líneas de abajo deja de cuadrar. */
.kpi .label { color: var(--muted); font-size: 11px; line-height: 1.35; text-transform: uppercase; letter-spacing: .5px; font-weight: 600; }
/* Cuando varios KPI van en fila, sus CIFRAS deben quedar a la misma altura.
   Un rótulo largo («DIVERGENCIA PREINFORME→FINAL») se parte en dos líneas y
   empujaba su número hacia abajo, dejando la fila desnivelada — el detalle que
   más delata a un panel hecho a la ligera. Se reservan exactamente dos líneas
   solo dentro de una grilla; un KPI suelto no paga el espacio. */
.grid .kpi .label {
  /* Altura FIJA de dos líneas, no mínima: con min-height, los acentos y los
     glifos de reserva movían la caja uno o dos píxeles y la fila volvía a
     desnivelarse. Es además una restricción sana del sistema: el rótulo de un
     KPI se lee de un vistazo, y lo que no cabe en dos líneas va en la línea de
     apoyo de abajo. */
  height: 2.7em;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
/* El ícono de ayuda mide 16 px: en un rótulo de 11 px con interlínea 1,35
   (14,85 px) agrandaba la línea donde cae y desnivelaba la cifra de esa
   tarjeta. Dentro del rótulo se ciñe a la interlínea; el blanco de pulsación
   de 24 px lo sigue dando su ::after, así que no se pierde área táctil. */
.kpi .label .help-btn { width: 1.2em; height: 1.2em; vertical-align: middle; }
/* El SVG global lleva vertical-align:-3px, que extiende la caja de línea por
   debajo de la base y agrandaba la línea donde cae el ícono. */
.kpi .label .help-btn svg { width: 1em; height: 1em; vertical-align: middle; }
.kpi .value { font-size: var(--fs-xl); font-weight: 700; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; line-height: 1.1; }
.kpi .sub { color: var(--muted); font-size: 12px; }
/* Escala ÚNICA de cifra: sm 20 / normal 27 / hero 40. Todas tabulares. */
.kpi--sm .value { font-size: var(--fs-lg); }
.kpi--hero .value { font-size: var(--fs-2xl); }

/* ── Tablas ──────────────────────────────────────────────── */
table { width: 100%; border-collapse: collapse; font-size: 13px; }
th, td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--line); vertical-align: top; }
th { color: var(--muted); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .5px; }
/* Encabezado fijo al hacer scroll dentro de .table-scroll (que da la altura). */
thead th { position: sticky; top: 0; z-index: 2; background: var(--card); }
/* La barra superior también es pegajosa (z-index 10): sin este desfase la
   cabecera de la tabla se metía DEBAJO de ella y desaparecía. El alto real lo
   mide midaBarra() en cada carga y al redimensionar, porque la barra es más
   baja en móvil. Solo aplica cuando la tabla cabe: si se desplaza en
   horizontal, la referencia es el envoltorio y el desfase sobraría. */
:root { --topbar-h: 59px; }
.table-scroll.cabe thead th { top: var(--topbar-h); }
/* …salvo cuando hay OTRO contenedor con scroll por encima: ahí el `th` se
   pega a 59 px del borde de ESE contenedor, o sea 59 px DENTRO de la tabla.
   Pasaba en /turnos (el envoltorio `.turnos-grilla` conserva overflow-x, y
   los rótulos LUN-DOM caían sobre los días 1 y 2) y en /diagnosticos (un
   `max-height` a mano, y el `th` tapaba la fila más frecuente e interceptaba
   sus clics). Ambos son escritorio, que es donde se hace la demo. */
.scrollport .table-scroll.cabe thead th,
.table-scroll.tall thead th { top: 0; }
/* `separate` es el requisito de Chrome para que el `th` pegajoso funcione.
   Con `border-spacing: 0` y bordes solo en `border-bottom`, se ve igual que
   con `collapse`: no hay bordes contiguos que se dupliquen. Solo en tablas
   largas (>=12 filas), marcadas por marcaDesborde(). */
.table-scroll.cabe.larga > table { border-collapse: separate; border-spacing: 0; }
tbody tr { transition: background var(--t-fast); }
tbody tr:nth-child(even) { background: color-mix(in srgb, var(--surface-2) 55%, transparent); }
tbody tr:hover { background: var(--surface-2); }
/* Cifras, fechas y horas alineadas y con ancho fijo (no "bailan" al cambiar). */
td { font-variant-numeric: tabular-nums; }
td.num, th.num { text-align: right; }

/* ── Badges ──────────────────────────────────────────────── */
.badge { display: inline-block; padding: 2px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; line-height: 1.5; }
.badge-ok { background: var(--ok-bg); color: var(--ok-ink); }
.badge-good { background: var(--good-bg); color: var(--good-ink); }
.badge-warn { background: var(--warn-bg); color: var(--warn-ink); }
.badge-danger { background: var(--danger-bg); color: var(--danger-ink); }
.badge-info { background: var(--info-bg); color: var(--info-ink); }
.badge-muted { background: var(--surface-2); color: var(--muted); }
.badge-neutral { background: var(--surface-2); color: var(--ink); border: 1px solid var(--line); }

/* ── Formularios y botones ───────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
  background: var(--accent); color: var(--on-accent); border: none;
  padding: 8px 15px; border-radius: var(--radius-sm); font-weight: 600; cursor: pointer; font-size: 13px;
  transition: background var(--t-fast), transform var(--t-fast), box-shadow var(--t-fast);
}
.btn:hover { background: var(--accent-2); text-decoration: none; box-shadow: 0 2px 8px rgba(8, 145, 178, 0.25); }
.btn:active { transform: translateY(1px); }
.btn.secondary { background: var(--card); color: var(--ink); border: 1px solid var(--field-border); box-shadow: none; }
.btn.secondary:hover { background: var(--surface-2); border-color: var(--muted); box-shadow: none; }
.btn:disabled { opacity: .5; cursor: not-allowed; transform: none; }
[data-theme='dark'] .btn { color: #03222b; }
[data-theme='dark'] .btn.secondary { color: var(--ink); }
/* Dos tamaños canónicos con área táctil ≥30px (WCAG 2.5.8), en vez de los
   ~30 padding/font-size distintos que había en las vistas. */
.btn.sm { padding: 5px 11px; font-size: 12px; min-height: 30px; }
.btn.icon { width: 32px; height: 32px; padding: 0; }
/* Micro-botón en celda densa: mantiene el tamaño visual pero amplía el blanco
   táctil con un pseudo-elemento invisible. */
.btn.tiny { padding: 3px 9px; font-size: 12px; position: relative; }
.btn.tiny::after { content: ''; position: absolute; inset: -7px; }
/* `width:100%` es para los campos que se RELLENAN, no para las casillas: una
   casilla de selección no tiene contenido que acomodar. Sin excluirlas, en
   modo tarjeta —donde la celda ocupa la ficha entera— la casilla de «marcar
   esta fila» de /seguridad se dibujaba de 301px de ancho, y como además lleva
   `flex-shrink:0` no podía encoger: 42% del rótulo + 100% de la casilla = 142%
   del ancho de la tarjeta. En una tabla de escritorio la columna es estrecha y
   el defecto pasaba por decoración. */
input:not([type='checkbox']):not([type='radio']), select, textarea {
  font: inherit; padding: 8px 10px; border: 1px solid var(--field-border); border-radius: var(--radius-sm);
  background: var(--card); color: var(--ink); width: 100%;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent-2); box-shadow: var(--focus); }
input::placeholder, textarea::placeholder { color: var(--placeholder); opacity: 1; }
/* Marcador de campo obligatorio (lo añade PA.enhanceForms sobre la etiqueta
   ya asociada). El color rojo no es el único portador: el asterisco es forma. */
label .req { color: var(--danger); margin-left: 3px; font-weight: 700; }
/* Botón enviando: la guarda global lo deshabilita tras serializar el POST. */
.btn.is-busy { opacity: .6; cursor: progress; }
label { font-size: 12px; color: var(--muted); font-weight: 600; display: block; margin-bottom: 4px; }
.field { margin-bottom: 12px; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end; }
.row > * { flex: 1; min-width: 120px; }
/* `min-width: 120px` congela el contenedor a 120 px aunque su contenido pida
   más, y si ese contenido lleva `nowrap` se desborda por debajo del vecino:
   en /feedback la casilla «Informe de fin de rotación» necesita 181 px y el
   botón «Guardar programación» —fondo blanco opaco— la tapaba, dejando leer
   «Informe de fin de». `.row > .fijo` se dimensiona por su contenido. */
.row > .fijo { flex: 0 0 auto; min-width: max-content; }

.toolbar { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; margin-bottom: 16px; }
.muted { color: var(--muted); }
.section-title { font-size: 12px; text-transform: uppercase; letter-spacing: .6px; color: var(--muted); margin: 26px 0 10px; font-weight: 700; }
.chart-wrap { position: relative; height: 280px; }
/* En móvil un gráfico de 280px ocupa un tercio de la pantalla y las etiquetas
   se amontonan; se le da algo más de aire vertical y menos en apaisado. */
@media (max-width: 640px) { .chart-wrap { height: 240px; } }
@media (max-width: 640px) and (orientation: landscape) { .chart-wrap { height: 200px; } }
/* Estado vacío del gráfico: reemplaza visualmente al canvas (que queda oculto,
   NO borrado, para poder volver a dibujar sin recargar). */
.chart-empty {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  text-align: center; padding: var(--s4); color: var(--muted); font-size: var(--fs-xs, 12.5px);
  border: 1px dashed var(--line); border-radius: var(--radius-sm); background: var(--surface-2);
}
.report-vs { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
@media (max-width: 800px) { .report-vs { grid-template-columns: 1fr; } }
/* Dos columnas que SÍ colapsan. Existía como `style` inline en urgente.ejs, sin
   media query: a 390 px la ficha del TM quedaba en dos columnas de 177 px, con
   el preinforme a cuatro palabras por línea y el correo del destinatario
   cortado («docente@demo.c»). El min-width:0 es obligatorio por lo ya
   documentado arriba para .grid > *. */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s4); align-items: start; }
.grid-2 > * { min-width: 0; }
@media (max-width: 800px) { .grid-2 { grid-template-columns: 1fr; } }
.report-box { white-space: pre-wrap; font-size: 13px; background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius-sm); padding: 14px; max-height: 520px; overflow: auto; }
.pill-list { display: flex; flex-wrap: wrap; gap: 6px; }

/* ── Markdown (feedback) ─────────────────────────────────── */
.markdown { font-size: 13.5px; line-height: 1.55; color: var(--ink); }
.markdown h2 { font-size: 16px; margin: 22px 0 8px; padding-bottom: 5px; border-bottom: 2px solid var(--accent); color: var(--accent-ink); }
.markdown h3 { font-size: 14px; margin: 14px 0 6px; }
.markdown p { margin: 8px 0; }
.markdown ul { margin: 8px 0; padding-left: 20px; }
.markdown li { margin: 3px 0; }
.markdown a { color: var(--accent-2); text-decoration: underline; word-break: break-word; }
.markdown table { margin: 12px 0; width: 100%; border-collapse: collapse; font-size: 12.5px; }
.markdown table td { border: 1px solid var(--line); padding: 6px 10px; }
.markdown table tr:first-child td { background: var(--surface-2); font-weight: 600; }
.markdown blockquote { margin: 12px 0; padding: 8px 14px; border-left: 4px solid var(--accent); background: var(--surface-2); border-radius: 0 6px 6px 0; }
.markdown blockquote p { margin: 4px 0; font-size: 13px; }
.markdown hr { border: none; border-top: 1px solid var(--line); margin: 18px 0; }
.markdown code { background: var(--surface-2); padding: 1px 5px; border-radius: 4px; font-size: 12px; }
.notice { padding: 12px 14px; border-radius: var(--radius-sm); background: var(--hl-warn); border: 1px solid var(--warn); color: var(--warn-ink); margin-bottom: 16px; }

/* ── Login ───────────────────────────────────────────────── */
.login-wrap { display: flex; align-items: center; justify-content: center; min-height: 100vh; background: radial-gradient(1200px 600px at 70% -10%, #134e5e33, transparent), var(--sidebar-bg); }
.login-card { background: var(--card); padding: 32px; border-radius: 16px; width: min(360px, calc(100vw - 28px)); box-shadow: var(--shadow-lg); }
@media (max-width: 480px) { .login-card { padding: 24px 20px; } }
.login-card h1 { font-size: 20px; margin: 0 0 4px; }
.spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid currentColor; border-top-color: transparent; border-radius: 50%; animation: spin .7s linear infinite; }

/* U11 — El fondo ámbar de .notice contradecía el significado cuando el
   mensaje era de éxito o error: variantes coherentes con los flashes. */
.notice.ok { background: var(--hl-ok, #dcfce7); border-color: var(--ok, #16a34a); color: var(--ok-ink, #14532d); }
.notice.error { background: var(--hl-danger, #fee2e2); border-color: var(--danger, #dc2626); color: var(--danger-ink, #7f1d1d); }
.notice.info { background: var(--hl-info, #e0f2fe); border-color: var(--accent, #0e7490); color: inherit; }
@keyframes spin { to { transform: rotate(360deg); } }

/* ── Command palette (⌘K) ────────────────────────────────── */
.cmdk-overlay {
  position: fixed; inset: 0; z-index: 100; background: rgba(2, 6, 23, 0.45);
  display: flex; justify-content: center; align-items: flex-start; padding-top: 14vh;
  backdrop-filter: blur(2px);
}
.cmdk {
  width: min(560px, calc(100vw - 32px)); background: var(--card); border: 1px solid var(--line);
  border-radius: 14px; box-shadow: var(--shadow-lg); overflow: hidden;
  animation: cmdk-in 140ms ease;
}
@keyframes cmdk-in { from { opacity: 0; transform: translateY(-8px) scale(.98); } }
.cmdk input {
  border: none; border-bottom: 1px solid var(--line); border-radius: 0;
  padding: 14px 16px; font-size: 15px; background: transparent;
}
.cmdk input:focus { box-shadow: none; border-color: var(--line); }
.cmdk-list { max-height: 320px; overflow-y: auto; padding: 6px; }
.cmdk-item {
  display: flex; align-items: center; gap: 10px; padding: 9px 10px; border-radius: 8px;
  cursor: pointer; font-size: 13.5px; color: var(--ink);
}
.cmdk-item svg { width: 16px; height: 16px; color: var(--muted); }
.cmdk-item.sel { background: var(--accent); color: var(--on-accent); }
.cmdk-item.sel svg { color: #fff; }
[data-theme='dark'] .cmdk-item.sel { color: #03222b; }
[data-theme='dark'] .cmdk-item.sel svg { color: #03222b; }
.cmdk-empty { padding: 18px; text-align: center; color: var(--muted); font-size: 13px; }
.cmdk-hint { padding: 8px 14px; border-top: 1px solid var(--line); color: var(--muted); font-size: 11px; display: flex; gap: 14px; }
.kbd {
  font-family: inherit; font-size: var(--fs-3xs); color: var(--muted); border: 1px solid var(--line);
  border-bottom-width: 2px; border-radius: 5px; padding: 1px 5px; background: var(--surface-2);
}
.search-btn {
  display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--muted); border-radius: 8px; padding: 6px 10px;
  font-size: 12.5px; cursor: pointer; transition: border-color var(--t-fast), color var(--t-fast);
}
.search-btn:hover { border-color: var(--muted); color: var(--ink); }
.search-btn svg { width: 14px; height: 14px; }
@media (max-width: 768px) { .search-btn .kbd { display: none; } }

/* ── Delta de KPI (▲/▼ vs semana anterior) ───────────────── */
.kpi .delta { font-size: 11.5px; font-weight: 600; }
.kpi .delta.up { color: var(--ok); }
.kpi .delta.down { color: var(--danger); }
.kpi .delta.flat { color: var(--muted); }

/* ── Ayuda contextual (globos «¿qué es esto?») ───────────── */
.help-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 16px; height: 16px; padding: 0; margin-left: 4px; vertical-align: -2px;
  border: none; background: none; color: var(--muted); cursor: help; border-radius: 50%;
  position: relative;
  opacity: 0.6; transition: opacity var(--t-fast), color var(--t-fast);
}
/* Blanco de pulsación de 24x24 sin cambiar el tamaño visible del ícono
   (WCAG 2.5.8): el ícono mide 16px pero el dedo tiene margen. */
.help-btn::after { content: ''; position: absolute; inset: -4px; border-radius: 50%; }
.help-btn:hover, .help-btn:focus-visible, .help-btn.active { opacity: 1; color: var(--accent); }
.help-btn svg { width: 14px; height: 14px; }
/* en encabezados de tabla, el ícono no debe heredar el uppercase/bold */
th .help-btn { vertical-align: middle; }

.help-pop {
  position: fixed; z-index: 120; width: min(300px, calc(100vw - 24px));
  background: var(--card); color: var(--ink); border: 1px solid var(--line);
  border-radius: 10px; box-shadow: var(--shadow-lg); padding: 12px 14px;
  font-size: 12.5px; line-height: 1.5; font-weight: 400; text-transform: none; letter-spacing: normal;
  opacity: 0; transform: translateY(-4px); pointer-events: none;
  transition: opacity var(--t-fast), transform var(--t-fast);
}
.help-pop.show { opacity: 1; transform: translateY(0); pointer-events: auto; }
.help-pop .help-title { display: block; font-weight: 700; font-size: 13px; color: var(--accent-ink); margin-bottom: 5px; }
[data-theme='dark'] .help-pop .help-title { color: var(--accent); }
.help-pop .help-what { display: block; color: var(--ink); }
.help-pop .help-how { display: block; margin-top: 7px; padding-top: 7px; border-top: 1px solid var(--line); color: var(--muted); }
.help-pop .help-how b { color: var(--ink); font-weight: 600; }

/* ── Tour de bienvenida (walkthrough) ────────────────────── */
/* 4 paneles oscuros alrededor del foco: el elemento resaltado queda al
   descubierto Y clickeable (los paneles bloquean el resto de la página). */
.tour-dim { position: fixed; z-index: 130; background: rgba(2, 6, 23, 0.62); transition: all 180ms ease; }
.tour-ring {
  position: fixed; z-index: 131; pointer-events: none; border-radius: 10px;
  border: 2px solid var(--accent-2);
  box-shadow: 0 0 0 4px rgba(8, 145, 178, 0.35), 0 0 22px rgba(8, 145, 178, 0.45);
  transition: all 180ms ease;
}
.tour-tip {
  position: fixed; z-index: 132; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow-lg);
  padding: 14px 16px; animation: tour-in 180ms ease;
}
@keyframes tour-in { from { opacity: 0; transform: translateY(6px); } }
.tour-tip-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; color: var(--accent-ink); }
[data-theme='dark'] .tour-tip-title { color: var(--accent); }
.tour-tip-text { font-size: 13px; line-height: 1.5; }
.tour-tip-row { display: flex; align-items: center; gap: 8px; margin-top: 12px; }
.tour-tip-row .btn { padding: 6px 12px; font-size: 12.5px; }
.tour-count { margin-left: auto; margin-right: 2px; color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }
.tour-skip { background: none; border: none; color: var(--muted); font-size: 12px; cursor: pointer; padding: 4px 0; }
.tour-skip:hover { color: var(--ink); text-decoration: underline; }

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

/* ── Impresión: solo el área marcada con .print-area ─────── */
.print-only { display: none; }
@media print {
  /* Siempre en claro al imprimir, aunque la app esté en oscuro */
  :root, [data-theme='dark'] {
    --bg: #fff; --card: #fff; --surface-2: #f8fafc; --ink: #0f172a; --muted: #64748b; --line: #e2e8f0;
    --accent: #0e7490; --accent-ink: #155e75;
  }
  body * { visibility: hidden; }
  .print-area, .print-area * { visibility: visible; }
  .print-only { display: block; }
  .print-area {
    position: absolute; left: 0; top: 0; width: 100%;
    margin: 0; padding: 0; border: none; box-shadow: none; background: #fff;
  }
  .print-header { border-bottom: 2px solid var(--accent); padding-bottom: 10px; margin-bottom: 8px; }
  .print-header h2 { font-size: 18px; color: var(--accent-ink); }
  .sidebar, .topbar, .toolbar, .nav { display: none !important; }
  .no-print { display: none !important; }
  .markdown a { color: #0369a1; text-decoration: underline; }
  .markdown table { width: 100%; }
  .markdown table, .markdown blockquote { page-break-inside: avoid; }
  .markdown h2, .markdown h3 { page-break-after: avoid; }
  @page { margin: 1.4cm; }
}

/* ── Mi Colección + Buscar (Fase 1) ─────────────────────── */
.tag-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 600;
  background: var(--accent-soft); color: var(--accent-ink);
  border: 1px solid var(--line); text-decoration: none; cursor: pointer;
}
.tag-chip.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.tag-chip .x { opacity: .6; font-weight: 400; }
mark { background: var(--warn-bg); color: var(--warn-ink); padding: 0 2px; border-radius: 3px; }
.sc-backdrop {
  position: fixed; inset: 0; background: rgba(15, 23, 42, .45); z-index: 90;
}
.sc-modal {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(440px, 92vw); z-index: 91; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 18px; box-shadow: 0 20px 60px rgba(0, 0, 0, .25);
}
.sc-modal label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 4px; }
.sc-modal input, .sc-modal textarea { width: 100%; }
.star-btn { background: none; border: none; cursor: pointer; padding: 2px; color: var(--muted); }
.star-btn.saved { color: #f59e0b; }
.star-btn.saved svg { fill: #f59e0b; }
/* El rótulo de un botón peligroso debe leerse en AMBOS temas: en oscuro
   --danger es un rojo claro (#f87171) y el blanco cae por debajo de AA. */
.btn.danger { background: var(--danger); color: var(--danger-on, #fff); }
.btn.danger:hover { background: var(--danger); filter: brightness(.92); box-shadow: 0 2px 8px rgba(220, 38, 38, .25); }

/* ── Panel de administración: secciones colapsables ──────────────── */
/* <details class="card adm-sec"> con <summary> = título clicable. El estado
   abierto/cerrado se persiste por sección en localStorage (ver admin.ejs). */
details.adm-sec { margin-bottom: 14px; }
details.adm-sec > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: -4px 0;
}
details.adm-sec > summary::-webkit-details-marker { display: none; }
details.adm-sec > summary .chev {
  flex: none; color: var(--muted); transition: transform .15s ease;
  display: inline-flex; font-size: 12px;
}
details.adm-sec[open] > summary .chev { transform: rotate(90deg); }
details.adm-sec > summary h3 { margin: 0; display: inline-flex; align-items: center; gap: 6px; flex-wrap: wrap; }
details.adm-sec > summary .sum-note { color: var(--muted); font-weight: 400; font-size: 12px; }
details.adm-sec > summary:hover h3 { color: var(--accent); }
details.adm-sec[open] > summary { margin-bottom: 12px; }

/* Índice de salto del panel (chips) */
/* Índice de salto de Administración.
   `top: 0` lo pegaba a la MISMA altura que la barra superior, que también es
   pegajosa y va por encima (z-index 10 contra 5): el índice quedaba detrás.
   En escritorio, con sus doce enlaces en una fila de ~30px contra una barra de
   59, desaparecía entero; en el teléfono, donde envuelve en varias filas,
   asomaban las de abajo y la primera salía cortada por la mitad. El desfase es
   el mismo `--topbar-h` que ya usa el encabezado de tabla, y el z-index queda
   entre la barra (10) y ese encabezado (2). */
.adm-index {
  position: sticky; top: var(--topbar-h); z-index: 6;
  display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  padding: 8px 2px; margin: 0 0 12px; background: var(--bg);
}
@media (max-width: 700px) {
  /* En un teléfono los doce chips envuelven en tres o cuatro filas: pegajoso
     se comería ~120px de pantalla de forma permanente para un atajo que se usa
     una vez. Se queda arriba, como índice de la página. */
  .adm-index { position: static; }
}
.adm-index a {
  font-size: 11.5px; padding: 3px 10px; border: 1px solid var(--line);
  border-radius: 999px; color: var(--muted); background: var(--card);
  text-decoration: none; white-space: nowrap;
}
.adm-index a:hover { color: var(--accent); border-color: var(--accent-2); }

/* Grupos de rotaciones por residente */
details.rot-group { border: 1px solid var(--line); border-radius: 8px; padding: 8px 12px; margin-bottom: 8px; }
details.rot-group > summary { cursor: pointer; list-style: none; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
details.rot-group > summary::-webkit-details-marker { display: none; }
details.rot-group[open] { padding-bottom: 4px; }
details.rot-group[open] > summary { margin-bottom: 6px; }

/* Buscador de filas dentro de una sección */
.adm-filter { max-width: 280px; padding: 6px 10px; font-size: 12.5px; }

/* ── Login: mostrar/ocultar contraseña + recordar correo ─────────── */
.pw-wrap { position: relative; }
.pw-wrap input { padding-right: 42px; }
.pw-toggle {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  background: none; border: none; cursor: pointer; color: var(--muted);
  padding: 5px; display: inline-flex; border-radius: 6px; line-height: 0;
}
.pw-toggle:hover { color: var(--ink); background: var(--surface-2); }
.login-remember {
  display: flex; align-items: center; gap: 7px; margin: 2px 0 14px;
  font-size: 12.5px; color: var(--muted); font-weight: 400; cursor: pointer;
}
.login-remember input { width: auto; margin: 0; }
.caps-warn { font-size: 11.5px; color: var(--warn-ink, #92400e); margin-top: 5px; }

/* ── Utilidades (reemplazan los patrones inline más repetidos) ────── */
.m0 { margin: 0 !important; }
.nowrap { white-space: nowrap; }
.f0 { flex: 0 !important; }
.f1 { flex: 1; }
.f2 { flex: 2; }
.t-3xs { font-size: var(--fs-3xs); }
.t-2xs { font-size: var(--fs-2xs); }
.t-xs { font-size: var(--fs-xs); }
.normal { font-weight: 400; }
.hd { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: var(--s3); }

/* ── Chips del calendario de turnos (tokenizados por tema) ────────── */
.shift-chip { display: inline-flex; align-items: center; gap: 4px; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; }
.shift-chip.habil { background: var(--info-bg); color: var(--info-ink); }
.shift-chip.finde { background: color-mix(in srgb, #7c3aed 16%, var(--card)); color: #7c3aed; }
.shift-chip.feriado { background: var(--danger-bg); color: var(--danger-ink); }
[data-theme='dark'] .shift-chip.finde { background: #2a1a4d; color: #c4b5fd; }

/* ── Estado vacío (tabla/lista sin resultados) ───────────────────── */
.empty-state { text-align: center; padding: 32px 18px; color: var(--muted); }
.empty-state svg { width: 34px; height: 34px; opacity: .5; margin-bottom: 8px; }
.empty-state .es-title { font-size: 14px; font-weight: 600; color: var(--ink); margin-bottom: 4px; }
.empty-state .es-hint { font-size: 12.5px; }

/* ── Barra de progreso de navegación (rendimiento percibido) ─────── */
#pa-navbar {
  position: fixed; top: 0; left: 0; height: 3px; width: 0;
  background: linear-gradient(90deg, var(--accent), var(--accent-2));
  z-index: 9999; transition: width .2s ease, opacity .3s ease; opacity: 0; pointer-events: none;
}
#pa-navbar.on { opacity: 1; }

/* ── Columnas OPCIONALES (.col-opt) ──────────────────────────────────
   Una tabla de 14 columnas no cabe en ningún ancho realista: la bitácora
   necesita ~1200 px y el PC del servicio da ~1000 útiles, así que «Acceso»
   —la única puerta al paciente real— quedaba cortada y «ver →» fuera de la
   pantalla. Bajo 1200 px se ocultan las columnas marcadas como opcionales
   (contexto útil pero no accionable: rotación, columnas vacías, duplicados de
   otra celda), y con eso la tabla vuelve a caber y el modo tarjeta se puede
   aplicar. La marca va en el `<th>` Y en su `<td>`: son la misma columna.
   El dato no se pierde — sigue en el export y en el detalle del caso. */
/* 1500 px y no 1200: el corte no es «móvil», es «¿cabe la tabla?». A 1280 px
   de ventana el contenido tiene ~1000 px útiles y la bitácora de 14 columnas
   necesita ~1200 — justo el rango 1000-1536 px del PC del servicio, que no
   tenía ninguna válvula (la barra lateral solo se pliega bajo 1000 y el
   compactado de celdas entra en ≤1000). Sobre 1500 px caben todas. */
@media (max-width: 1500px) {
  table th.col-opt, table td.col-opt { display: none; }
  /* Gana al `display:block` que el modo tarjeta pone en todos los td. */
  table.cards-sm td.col-opt { display: none; }
  /* …salvo que se pidan explícitamente. Ocultar CONTEXTO está bien; ocultar un
     CONTROL lo hace inexistente, y en estas tablas hay casillas de permiso y
     campos que solo viven aquí (una casilla que nadie encuentra es una función
     que no existe). El interruptor lo pone app.js encima de cada tabla que
     esté escondiendo algo, y dice CUÁNTAS columnas y cuáles. */
  table.col-opt-on th.col-opt, table.col-opt-on td.col-opt { display: table-cell; }
  table.cards-sm.col-opt-on td.col-opt { display: table-cell; }
}
/* Bajo 640 px el modo tarjeta apila las celdas: ahí «table-cell» rompería la
   tarjeta, así que las reveladas vuelven a comportarse como fila de tarjeta. */
@media (max-width: 640px) {
  table.cards-sm.col-opt-on td.col-opt { display: block; }
}
.col-opt-btn {
  background: none; border: 0; padding: 2px 0; margin: 0 0 6px;
  color: var(--accent); font: inherit; font-size: 12px; cursor: pointer;
}
.col-opt-btn:hover { text-decoration: underline; }

/* ── Tablas en modo TARJETA en móvil (opt-in con .cards-sm) ──────────
   Bajo 640px, cada fila se vuelve una tarjeta apilada; cada celda muestra su
   etiqueta (data-label) a la izquierda. Evita el scroll horizontal en las
   pantallas que el TM y el residente usan de pie en el hospital. La columna con
   la acción principal se marca .cell-action → botón a ancho completo. */
@media (max-width: 640px) {
  table.cards-sm, table.cards-sm thead, table.cards-sm tbody,
  table.cards-sm tr, table.cards-sm td { display: block; width: 100%; }
  table.cards-sm thead { position: absolute; left: -9999px; }  /* encabezado oculto: cada celda trae su rótulo */
  table.cards-sm tr {
    border: 1px solid var(--line); border-radius: var(--radius-sm);
    margin-bottom: 10px; padding: 6px 10px; background: var(--card);
  }
  table.cards-sm tr:nth-child(even) { background: var(--card); }  /* sin zebra en modo tarjeta */
  /* El rótulo FLOTA; el valor fluye a su lado como texto normal.
     Los otros dos mecanismos fallan, cada uno a su manera:
       · FLEX — el valor es un ítem ANÓNIMO, así que no hay nada a lo que
         ponerle `min-width:0` y no puede encoger por debajo de su contenido.
         La única palanca era `overflow-wrap: anywhere`, que parte por
         CUALQUIER carácter: «Mamografía» salía «Mamogr / afía» dentro de su
         píldora, el centro «SJD» como «SJ / D» y hasta «TC» en dos líneas.
       · GRID — cada elemento HIJO se convierte en un ítem propio, así que una
         celda con texto + insignia rompía en dos filas y la insignia se
         estiraba al ancho de la columna del rótulo. Se vio en «Conclusión
         errónea» + «crítico» del resumen.
     Con un float el contenido del valor sigue siendo flujo inline normal —las
     insignias no se estiran ni se separan— y el bloque puede encogerse sin
     `anywhere`, así que basta `break-word`: parte solo la palabra que no cabe
     entera. `flow-root` para que la celda contenga su propio float. */
  table.cards-sm td {
    border: none; padding: 5px 0;
    display: flow-root; text-align: right;
  }
  /* Una insignia es una unidad: si no cabe, que se salga un poco antes que
     partirse por la mitad y dejar de leerse como una etiqueta. */
  table.cards-sm td .badge { white-space: nowrap; }
  table.cards-sm td::before {
    content: attr(data-label); text-align: left; color: var(--muted);
    font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .3px;
    /* `0 1 42%`, no `0 0 42%`: con un valor largo el rótulo cede ancho en vez
       de empujar el valor fuera de la ficha. */
    float: left; width: 40%; padding-right: 10px; box-sizing: border-box;
    overflow-wrap: break-word;
  }
  /* ── Que la ficha NO pueda desplazarse hacia el lado ─────────────
     Medido: /validar y /duplicados se salían 294px, /bitacora-global 198,
     /admin 196, /seguridad 140. Y el desborde no se queda en lo cosmético:
     `.table-scroll` lo absorbe con overflow-x, así que la página no se mueve
     pero la FICHA sí, y al correrse se lleva fuera de pantalla su 42% de
     rótulos. El usuario queda leyendo «4426082229, …» y «sin validar» sin
     saber de qué columna son. Por eso aquí el scroll horizontal no es una
     válvula: es exactamente el fallo que el modo tarjeta venía a quitar. */
  table.cards-sm td { min-width: 0; overflow-wrap: anywhere; white-space: normal; }
  /* Un <select> nativo se dimensiona por su opción MÁS LARGA, que el diseño no
     conoce: basta un «DRA. MARIA FRANCISCA NORAMBUENA (inactivo)» en el roster
     para que toda la fila mida 294px de más, en tres pantallas distintas. */
  /* `min-width:0` además de `max-width`: dentro de la ficha la celda es un
     contenedor flex, y un hijo de flex no encoge por debajo de su contenido
     mientras conserve el `min-width:auto` por defecto. */
  table.cards-sm td select, table.cards-sm td input, table.cards-sm td textarea { max-width: 100%; min-width: 0; }
  /* Celda de una columna sin encabezado: nada que rotular, ficha completa. */
  table.cards-sm td.cell-plena { display: block; width: 100%; text-align: right; }
  table.cards-sm td.cell-plena::before { display: none; }
  table.cards-sm td.cell-action { display: block; text-align: center; padding-top: 8px; }
  table.cards-sm td.cell-action::before { display: none; }
  table.cards-sm td.cell-action .btn { width: 100%; }
  /* el min-width de scroll no aplica en modo tarjeta */
  .table-scroll table.cards-sm { min-width: 0; }
}

/* ── Vista global de rotaciones: residente × mes ──────────────────
   Es una matriz, no una lista: lo que informa es comparar FILAS (tres personas
   en Tórax el mismo mes, nadie en Urgencia). Por eso `no-cards` —como fichas
   dejaría de ser una matriz— con la columna de nombres fija, para recorrerla
   a lo ancho sin perder de vista de quién es la fila. */
.rot-tl { font-size: var(--fs-2xs); border-collapse: separate; border-spacing: 0; }
.rot-tl th, .rot-tl td { padding: 3px 4px; text-align: center; border-bottom: 1px solid var(--line); }
.rot-tl th:first-child, .rot-tl td:first-child {
  position: sticky; left: 0; z-index: 1; background: var(--card);
  text-align: left; white-space: nowrap; padding-right: 10px;
}
.rot-tl td:first-child a { color: var(--ink); text-decoration: none; font-weight: 600; }
.rot-tl td:first-child a:hover { color: var(--accent); text-decoration: underline; }
.rot-tl thead th { position: sticky; top: 0; z-index: 2; background: var(--card); font-size: var(--fs-3xs); }
.rot-tl thead th:first-child { z-index: 3; }
.rot-anio { display: block; font-size: 9px; opacity: .6; font-weight: 400; }
/* El mes en curso: la referencia desde la que se lee todo lo demás. */
.rot-tl .rot-hoy { box-shadow: inset 1px 0 0 var(--accent), inset -1px 0 0 var(--accent); }
.rot-cel { min-width: 30px; font-weight: 600; color: var(--ink); }
/* Ausencia: trama diagonal además del color, para no depender de él. */
.rot-aus { background-image: repeating-linear-gradient(45deg, transparent 0 3px, rgba(100,116,139,.35) 3px 5px); }
/* «Fuera de su programa» y «sin cubrir» son cosas distintas y tienen que
   verse distintas: confundirlas era el defecto. El primero se apaga —no
   corresponde—; el segundo lleva un «?» y un fondo de atención, porque es un
   mes que alguien tiene que llenar. */
.rot-fuera { background: repeating-linear-gradient(45deg, var(--surface-2) 0 4px, transparent 4px 8px); opacity: .5; }
.rot-hueco { background: var(--warn-bg); color: var(--warn-ink); font-weight: 700; }

/* Diez colores estables, uno por nombre de rotación (colorRotacion()). Son
   fondos suaves con la tinta del tema encima: la celda lleva SIEMPRE su
   abreviatura, así que el color acompaña, no informa solo. */
.rot-c0 { background: rgba(14,116,144,.20); }
.rot-c1 { background: rgba(217,119,6,.20); }
.rot-c2 { background: rgba(22,163,74,.20); }
.rot-c3 { background: rgba(124,58,237,.20); }
.rot-c4 { background: rgba(219,39,119,.20); }
.rot-c5 { background: rgba(2,132,199,.20); }
.rot-c6 { background: rgba(202,138,4,.22); }
.rot-c7 { background: rgba(5,150,105,.20); }
.rot-c8 { background: rgba(147,51,234,.18); }
.rot-c9 { background: rgba(190,18,60,.18); }

.rot-leyenda { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: 10px; font-size: var(--fs-3xs); color: var(--muted); }
.rot-leyenda span { display: inline-flex; align-items: center; gap: 5px; }
.rot-leyenda i { width: 12px; height: 12px; border-radius: 3px; border: 1px solid var(--line); flex: none; }

/* ── Cuadrícula semanal (bloques fusionados) ──────────────────────
   El eje son SEMANAS, no meses del calendario: las rotaciones se definen en
   semanas y se encadenan, así que su cadencia se corre respecto del mes. Las
   semanas seguidas de la misma rotación se fusionan en una sola celda con
   `colspan`, que es lo que hace que una rotación de cuatro semanas se lea
   como una rotación y no como cuatro cuadritos sueltos.

   `table-layout: fixed` + `<colgroup>` no es cosmético: sin él, el nombre
   dentro de un bloque fusionado empuja el ancho de columna y descuadra la
   matriz entera. Los anchos los emite la vista, para que la línea de «hoy»
   —posicionada en píxeles— no se pueda desincronizar de la cuadrícula. */
.rot-sem { table-layout: fixed; }
.rot-sem th, .rot-sem td { padding: 2px 3px; overflow: hidden; }
.rot-sem thead tr:first-child th { top: 0; height: 17px; line-height: 13px; }
.rot-sem thead tr + tr th { top: 17px; height: 16px; line-height: 12px; }
/* Rótulo del mes: abarca sus semanas y va alineado a la izquierda de su tramo,
   que es donde el ojo lo busca. */
.rot-sem .rot-mes { text-align: left; padding-left: 5px; border-left: 1px solid var(--line); font-weight: 700; }
.rot-sem .rot-dia { color: var(--muted); font-weight: 400; }
/* El bloque: una sola celda por rotación, con separación visible entre bloques
   contiguos para que se vea dónde termina una y empieza la otra. */
.rot-blq { border-left: 1px solid var(--card); font-weight: 600; color: var(--ink); height: 22px; }
.rot-txt { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Bloques de una o dos semanas: tipo más chica para que la abreviatura entre
   entera en vez de salir cortada. El servidor ya eligió la abreviatura para
   estos anchos (etiquetaBloque), esto solo la hace caber. */
.rot-blq[colspan='1'], .rot-blq[colspan='2'] { padding-left: 1px; padding-right: 1px; }
.rot-blq[colspan='1'] .rot-txt, .rot-blq[colspan='2'] .rot-txt { font-size: 9px; letter-spacing: -.2px; }
/* Línea de HOY: la referencia desde la que se lee todo lo demás. Va sobre la
   cuadrícula y no dentro de una celda, porque un bloque fusionado puede
   atravesar la semana en curso y marcar el bloque entero sería mentir. */
/* El lienzo fija los tres anchos de la cuadrícula en un solo sitio: el
   `<colgroup>` los lee, la tabla ocupa su suma y la línea de «hoy» se desplaza
   con ellos. Angostar la columna de nombres en móvil mueve las tres a la vez;
   con píxeles sueltos, la línea se habría quedado marcando otra semana. */
.rot-lienzo {
  position: relative;
  /* --rot-sem/--rot-fin/--rot-n los sobrescribe la vista en el `style` del
     lienzo (el número de columnas solo lo sabe ella); aquí quedan declarados
     con el valor habitual para que la hoja no dependa de que alguien los pase
     y para que ningún token se comporte como un valor por defecto invisible. */
  --rot-nom: 160px;
  --rot-sem: 24px;
  --rot-fin: 62px;
  --rot-n: 26;
  width: calc(var(--rot-nom) + var(--rot-n) * var(--rot-sem) + var(--rot-fin));
}
.rot-lienzo > table { width: 100%; }
/* En pantalla chica la columna fija no puede comerse media pantalla. */
@media (max-width: 700px) { .rot-lienzo { --rot-nom: 116px; } }
/* El recorte global de .matriz-nombre (96px) sobra aquí: el ancho ya lo pone
   la columna, y clavarlo en 96 dejaría el resto de la celda en blanco. */
.rot-tl .matriz-nombre { max-width: none; }
.rot-hoy-linea { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--accent); opacity: .75; pointer-events: none; z-index: 4; }
.rot-hoy-linea::before {
  content: 'hoy'; position: absolute; top: -1px; left: 3px;
  font-size: 9px; font-weight: 700; color: var(--accent); background: var(--card);
  padding: 0 3px; border-radius: 3px; line-height: 13px;
}
/* Vista por bloque de programa (1.ª rotación, 2.ª, 3.ª…): mismo lenguaje
   visual, pero el eje ya no es el calendario. */
.rot-sinblq { background: repeating-linear-gradient(45deg, var(--surface-2) 0 4px, transparent 4px 8px); opacity: .45; }
/* Conmutador entre las dos vistas. */
.rot-vistas { display: inline-flex; border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
.rot-vistas a { padding: 5px 11px; font-size: var(--fs-2xs); color: var(--muted); text-decoration: none; background: var(--card); white-space: nowrap; }
.rot-vistas a + a { border-left: 1px solid var(--line); }
.rot-vistas a[aria-current='page'] { background: var(--accent); color: #fff; font-weight: 600; }

/* ── Cabecera del comparador: cada dato con su rótulo ─────────────
   Era una tirada de texto gris con todo al mismo peso («Kevin Toala ·
   28-07-2026 · Staff: DRA. … · Turnaround 2,2 h») y debajo otra con la
   contabilidad interna de la atribución. Con rótulo delante, cada dato se
   puede buscar con la vista en vez de leerse entero. */
.par-meta { display: flex; flex-wrap: wrap; gap: 4px 18px; margin: 0; }
.par-meta > div { display: flex; gap: 6px; align-items: baseline; min-width: 0; }
.par-meta dt {
  color: var(--muted); font-size: var(--fs-3xs); text-transform: uppercase;
  letter-spacing: .3px; font-weight: 600; white-space: nowrap;
}
.par-meta dd { margin: 0; font-size: var(--fs-xs); min-width: 0; overflow-wrap: break-word; }
.par-atrib { margin-top: 8px; }
.par-atrib > summary {
  cursor: pointer; color: var(--muted); font-size: var(--fs-2xs);
  display: inline-flex; align-items: center; gap: 6px;
}
.par-atrib > summary:hover { color: var(--accent); }
.par-atrib[open] > summary { margin-bottom: 6px; }

/* ── Lo que no cabe DENTRO del gráfico, debajo y en HTML ──────────
   Un título de eje Y se dibuja rotado: su ancho de texto consume el alto de
   la escala, y Chart.js no lo trunca — lo centra y el lienzo lo recorta. En un
   teléfono «Diagnósticos y críticos por informe» (181px) contra una escala de
   118px de alto salía cortado por los dos extremos. Sacarlo del lienzo no
   pierde información: aquí se lee entero, se selecciona y lo lee un lector de
   pantalla. Lo mismo con la anotación de cambio de modelo (R15.3), que se
   escribía encima de las líneas. */
.chart-ejes, .chart-nota {
  font-size: var(--fs-2xs); margin: 4px 0 0; line-height: 1.4;
}
.chart-nota { font-variant-numeric: tabular-nums; }

/* ── Campos con ancho mínimo, pero solo donde cabe ────────────────
   Un `style="min-width:340px"` en línea gana a CUALQUIER regla de la hoja,
   incluida la del modo tarjeta: por eso la plantilla de URL del PACS seguía
   empujando la fila 340px en el teléfono y arrastrando la ficha con sus
   rótulos fuera de pantalla. El ancho mínimo existe por una razón real —una
   URL con `{accession}` es ilegible en 100px—, así que no se quita: se
   condiciona a que haya sitio. */
.campo-ancho { min-width: 340px; width: 100%; }
.campo-medio { min-width: 120px; }
@media (max-width: 900px) {
  .campo-ancho, .campo-medio { min-width: 0; width: 100%; }
}

/* ── Barra de acciones en lote de la bandeja (R32.1) ──────────────
   Aparece solo con algo seleccionado. Envuelve: en el teléfono son cinco
   botones y en una sola fila se saldrían por la derecha. */
.msg-bulk {
  display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap;
  margin: 0 0 var(--s2); padding: var(--s2) var(--s3);
  border: 1px solid var(--accent); border-radius: var(--radius-sm);
  background: var(--hl);
}
.msg-bulk .btn { flex: 0 1 auto; }
@media (max-width: 560px) {
  /* «Quitar selección» pierde su `margin-left:auto` al envolver: a ancho
     completo queda claro que cierra la barra. */
  .msg-bulk .btn:last-child { margin-left: 0 !important; flex: 1 0 100%; }
}

/* ── Matriz de calibración (staff × residente) ────────────────────
   Es un mapa de calor, no una lista: la información está en comparar la fila
   con la columna, así que convertirla en fichas la destruye y por eso lleva
   `no-cards`. Lo que sí necesita es poder recorrerse con el pulgar.

   Antes: la columna de nombres iba con `white-space:nowrap` y nombres
   completos («DRA. MARIA FRANCISCA NORAMBUENA»), así que a 390px ocupaba unos
   250 de 390 px y dejaba sitio para UNA columna y media de residentes. Una
   matriz con una celda visible no es una matriz.

   Ahora la primera columna queda fija (se ve siempre de qué staff es la fila
   mientras se desplaza a lo ancho) y acotada, con el nombre completo en el
   `title`. El fondo del sticky es `--card`, no `--bg`: la tabla vive DENTRO de
   una tarjeta y con el color de página se veía una costura al desplazar. */
.matriz-cal th:first-child, .matriz-cal td:first-child {
  position: sticky; left: 0; z-index: 1;
  background: var(--card); white-space: nowrap;
}
/* El recorte va en un elemento INTERIOR, no en la celda: `max-width` sobre un
   <td> en una tabla de layout automático es una sugerencia que el navegador
   ignora si el contenido no cabe (medido: la columna seguía en 154px pedidos
   104). Sobre un bloque dentro de la celda sí manda, y la celda se encoge. */
.matriz-nombre { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@media (max-width: 900px) {
  .matriz-nombre { max-width: 96px; }
  .matriz-cal th, .matriz-cal td { padding: 4px 6px; }
}

/* ── Nada más ancho que su sitio ──────────────────────────────────
   Tres formas de salirse del ancho que aparecieron repetidas en la auditoría
   de móvil (tools/movil-desborde.mjs), arregladas donde nacen y no pantalla
   por pantalla:

   1. El <select> nativo se dimensiona por su opción más larga. Un nombre de
      residente de 44 caracteres en el desplegable de reasignación bastaba
      para desbordar /validar, /duplicados, /pares/:id y el roster de
      administración — cuatro pantallas, una causa.
   2. Las cadenas sin espacios no parten: correos institucionales
      (francisca.bustamante@redsalud.gob.cl), URLs de PACS y listas de N° de
      acceso. `anywhere` corta donde haga falta antes que romper la caja.
   3. Un hijo de flex no puede encoger por debajo de su contenido mientras no
      tenga min-width:0 — el valor por defecto es `auto`, que significa «no
      encojas». Es la causa de los 203px de la cabecera del comparador. */
select { max-width: 100%; }
code, kbd, samp, .mono { overflow-wrap: anywhere; }
/* Cabeceras de tarjeta con dato largo: el texto cede antes que la caja. */
.card > h2, .card > h3 { overflow-wrap: anywhere; }

/* ── Navegación de período del calendario (seminarios) ────────────
   «← [27-jul – 02-ago de 2026] → Hoy». La etiqueta lleva ancho fijo a
   propósito: sin él las flechas saltan al cambiar de período, porque
   «agosto de 2026» y «27-jul – 02-ago de 2026» no miden lo mismo. Pero ese
   ancho más los tres botones suma ~360px y en un teléfono empujaba «Hoy»
   fuera de la pantalla. Bajo 560px la etiqueta pasa a su propia línea y los
   botones se reparten debajo: se conserva la estabilidad donde importa y
   cabe donde no cabía. */
.cal-nav { display: flex; gap: 6px; align-items: center; align-self: flex-end; padding-bottom: 1px; margin-left: auto; flex-wrap: wrap; }
.cal-nav > .btn { padding: 7px 12px; }
.cal-nav > b { min-width: 210px; text-align: center; }
@media (max-width: 560px) {
  .cal-nav { width: 100%; margin-left: 0; justify-content: space-between; }
  /* La etiqueta primero y a lo ancho; los botones en su propia fila. */
  .cal-nav > b { order: -1; flex: 1 0 100%; min-width: 0; }
  .cal-nav > .btn { flex: 1 1 auto; }
}

/* ── Panel de filtros plegable ────────────────────────────
   Diez campos de filtro en un teléfono empujan los datos fuera de la pantalla.
   Se pliegan por defecto en pantalla angosta (lo decide PA.collapsibleFilters)
   y el resumen indica cuántos filtros hay puestos, para que nadie interprete
   una lista filtrada como si fuera la lista completa. */
details.filtros { margin-top: var(--s3); }
details.filtros > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  padding: var(--s2) var(--s3); margin: 0 calc(-1 * var(--s3));
  border-radius: var(--radius-sm); font-size: var(--fs-xs); font-weight: 600;
  color: var(--ink); transition: background var(--t-fast);
}
details.filtros > summary::-webkit-details-marker { display: none; }
details.filtros > summary:hover { background: var(--surface-2); }
/* Cheurón propio, que gira al abrir (el marcador nativo no se puede estilar). */
details.filtros > summary::after {
  content: ''; margin-left: auto; width: 8px; height: 8px;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translate(-2px, -2px); transition: transform var(--t-fast);
}
details.filtros[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
@media (prefers-reduced-motion: reduce) { details.filtros > summary::after { transition: none; } }

/* ── Turnos: agenda en móvil / grilla en escritorio ───────
   La grilla mensual necesita 840 px (7 columnas legibles). En un teléfono se
   veían tres días y había que arrastrar: un calendario que no cabe entero no
   sirve para lo único que se le pide, saber quién está de turno. Debajo de
   860 px se cambia por una agenda vertical con los mismos datos y los mismos
   controles de edición. Ninguna de las dos se duplica en pantalla. */
.turnos-agenda { display: none; }
.turnos-grilla { overflow-x: auto; }
@media (max-width: 860px) {
  .turnos-grilla { display: none; }
  .turnos-agenda { display: block; list-style: none; margin: 0; padding: 0; }
  .ag-dia {
    display: flex; gap: var(--s3); padding: var(--s3) 0;
    border-bottom: 1px solid var(--line);
  }
  .ag-dia:last-child { border-bottom: none; }
  .ag-dia.es-vacio { padding: var(--s2) 0; opacity: .72; }
  .ag-dia.es-finde { background: color-mix(in srgb, var(--accent-soft) 45%, transparent); }
  .ag-dia.es-feriado { background: var(--hl-danger); }
  .ag-dia.es-hoy { box-shadow: inset 3px 0 0 var(--accent); }
  .ag-fecha {
    flex: 0 0 44px; text-align: center; display: flex; flex-direction: column;
    align-items: center; justify-content: flex-start; line-height: 1.1;
  }
  .ag-num { font-size: var(--fs-lg); font-weight: 700; font-variant-numeric: tabular-nums; }
  .ag-dow { font-size: var(--fs-3xs); color: var(--muted); text-transform: uppercase; letter-spacing: .4px; }
  .ag-cuerpo { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: var(--s1); }
  .ag-tit { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; font-weight: 600; font-size: var(--fs-xs); }
  .ag-dia.es-vacio .ag-tit { font-weight: 500; color: var(--muted); }
  .ag-linea { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
  .ag-linea .shift-chip { flex: 1 1 auto; min-width: 0; }
  .ag-f { margin: 0; }
  .ag-staff {
    flex: 1 1 auto; font-size: var(--fs-2xs); color: var(--muted);
    padding: 2px 8px; border-radius: var(--radius-sm);
    background: var(--surface-2); border: 1px dashed var(--line);
  }
  .ag-add { display: flex; gap: var(--s2); align-items: center; margin-top: var(--s1); }
  .ag-add select { flex: 1; min-width: 0; font-size: var(--fs-2xs); }
}
/* Navegación de mes: sin envolver, los cinco controles desbordaban la página. */
.turnos-nav { display: flex; gap: var(--s2); align-items: center; flex-wrap: wrap; }
.turnos-mes { font-size: var(--fs-sm); white-space: nowrap; }
.turnos-sep { border-left: 1px solid var(--line); height: 22px; }
@media (max-width: 640px) { .turnos-sep { display: none; } }

/* ── «Desde tu última visita» ─────────────────────────────
   La plataforma mostraba siempre el mismo estado agregado: quien entraba no
   tenía forma de saber si había algo nuevo. Esta franja convierte los datos
   que ya se registran (análisis, validaciones, sugerencias, asignaciones) en
   el relato de lo ocurrido mientras no estaba. Solo aparece si de verdad pasó
   algo, y se puede descartar por el resto de la sesión. */
.novedades { margin-bottom: var(--s4); }
.nov-cab { display: flex; align-items: flex-start; gap: var(--s3); }
.nov-cab h3 { margin: 0; font-size: var(--fs-sm); display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }
.nov-cuando { font-weight: 400; font-size: var(--fs-2xs); }
.nov-x {
  margin-left: auto; border: none; background: none; color: var(--muted);
  cursor: pointer; font-size: var(--fs-xs); line-height: 1; padding: var(--s1);
  border-radius: var(--radius-sm); position: relative;
}
.nov-x::after { content: ''; position: absolute; inset: -6px; }  /* área táctil */
.nov-x:hover { color: var(--ink); background: var(--surface-2); }
.nov-lista { list-style: none; margin: var(--s2) 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--s2) var(--s5); }
.nov-lista a {
  display: inline-flex; align-items: center; gap: var(--s2);
  font-size: var(--fs-xs); color: var(--ink); text-decoration: none;
  padding: var(--s1) 0;
}
.nov-lista a:hover { color: var(--accent); text-decoration: none; }
.nov-lista a b { font-variant-numeric: tabular-nums; font-size: var(--fs-sm); color: var(--accent); }
.nov-lista svg { color: var(--muted); }
@media (max-width: 640px) { .nov-lista { flex-direction: column; gap: var(--s1); } }

/* ── Áreas táctiles medidas en el barrido de 66 páginas ───
   El pie del menú («Cambiar contraseña», «Ver guía de bienvenida») medía
   200x17: aparece en las 65 páginas, así que era el objetivo por debajo del
   mínimo más repetido de toda la plataforma (WCAG 2.5.8 pide 24x24). */
.sidebar .userbox-link {
  display: flex; align-items: center; min-height: 24px;
  font-size: var(--fs-3xs); color: var(--sidebar-muted); text-decoration: none;
}
.sidebar .userbox-link:hover { color: var(--sidebar-ink); text-decoration: underline; }

/* Las casillas y radios usaban el tamaño por defecto del navegador (13x13):
   demasiado pequeños para el dedo y visualmente pobres al lado de campos de
   38px. min-* (no width/height) para no pelear con los `style="width:auto"`
   que varias vistas ponen en línea. */
input[type='checkbox'], input[type='radio'] {
  min-width: 18px; min-height: 18px; accent-color: var(--accent);
  cursor: pointer; flex-shrink: 0;
}
/* Una casilla dentro de su etiqueta: el blanco de pulsación es la etiqueta
   entera, así que esa es la que debe llegar al mínimo. */
label > input[type='checkbox'], label > input[type='radio'] { margin-right: var(--s1); }
label:has(> input[type='checkbox']), label:has(> input[type='radio']) {
  display: inline-flex; align-items: center; min-height: 24px; cursor: pointer;
}
/* Controles compactos: 21-22px de alto quedaban justo bajo el mínimo. */
.btn.sm, .btn.tiny, select, input[type='number'], input[type='date'] { min-height: 24px; }

/* ── R24.4 · «¿Llegó tu enseñanza?» (panel docente) ───────
   Tres cifras que se leen de un vistazo. Rejilla auto-fit para que en el
   celular caigan una bajo otra sin media query propia. */
.alcance-cifras {
  display: grid; gap: var(--s2);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}
.alcance-cifras > div {
  padding: var(--s2); border: 1px solid var(--line); border-radius: 8px;
  background: var(--surface-2); min-width: 0;
}
.alcance-cifras .n {
  display: block; font-size: var(--fs-xl); font-weight: 700; line-height: 1.1;
  color: var(--accent); font-variant-numeric: tabular-nums;
}
.alcance-cifras .t { display: block; font-size: var(--fs-sm); font-weight: 600; margin-top: 2px; }
.alcance-cifras .p { display: block; font-size: var(--fs-3xs); color: var(--muted); margin-top: 2px; }
/* En dos columnas la tercera cifra quedaba huérfana a media fila: se le deja
   ocupar el ancho completo en vez de dejar un hueco. */
@media (max-width: 640px) {
  .alcance-cifras { grid-template-columns: 1fr 1fr; }
  .alcance-cifras > div:nth-child(3):last-child { grid-column: 1 / -1; }
}
.alcance-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.alcance-lista li {
  display: flex; gap: 8px; align-items: center; flex-wrap: wrap;
  font-size: var(--fs-sm); padding: 3px 0; min-height: 24px;
}
.alcance-lista .muted { font-size: var(--fs-xs); }

/* ── R38 · Hitos del residente ───────────────────────────────
   Verde sobrio y una sola línea de acento: es una constatación clínica, no
   una medalla. Si esto brillara demasiado competiría con las alertas rojas,
   que son las que tienen que ganar la atención. */
.hitos { display: flex; flex-direction: column; gap: 8px; margin-bottom: 16px; }
.hito {
  display: flex; gap: 10px; align-items: flex-start;
  padding: 10px 14px; border: 1px solid var(--line); border-left: 3px solid var(--ok, #16a34a);
  border-radius: 8px; background: var(--surface);
}
.hito-i { color: var(--ok, #16a34a); display: flex; flex-shrink: 0; margin-top: 1px; }
.hito-i svg { width: 17px; height: 17px; }
.hito b { display: block; font-size: var(--fs-xs); line-height: 1.35; }
.hito .muted { display: block; font-size: var(--fs-3xs); margin-top: 2px; }

/* ── UX-4 · «Análisis detallado» plegable (ficha del residente) ───
   Mismo lenguaje que details.filtros pero como tarjeta: aquí el plegable ES
   la sección, no un accesorio dentro de una. */
details.detalle-analitico > summary {
  cursor: pointer; list-style: none; user-select: none;
  display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap;
  min-height: 24px; padding: var(--s2) var(--s3);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); font-size: var(--fs-xs); color: var(--ink);
  transition: background var(--t-fast);
}
details.detalle-analitico > summary::-webkit-details-marker { display: none; }
details.detalle-analitico > summary:hover { background: var(--surface-2); }
details.detalle-analitico > summary .muted { font-size: var(--fs-3xs); }
details.detalle-analitico > summary::after {
  content: ''; margin-left: auto; width: 8px; height: 8px; flex-shrink: 0;
  border-right: 2px solid var(--muted); border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translate(-2px, -2px); transition: transform var(--t-fast);
}
details.detalle-analitico[open] > summary::after { transform: rotate(-135deg) translate(-2px, -2px); }
details.detalle-analitico[open] > summary { margin-bottom: var(--s3); }
@media (prefers-reduced-motion: reduce) { details.detalle-analitico > summary::after { transition: none; } }

/* ── UX-5 · Camino de primer uso (residente sin capturas) ────
   Pasos numerados con el número como ancla visual. Es la única pantalla que
   verá un residente nuevo, así que se le da aire en vez de apretarla. */
.primer-uso { border-left: 4px solid var(--accent); }
.primer-uso .pasos { list-style: none; counter-reset: paso; margin: 0; padding: 0; }
.primer-uso .pasos > li {
  counter-increment: paso; position: relative;
  padding: 0 0 var(--s3) 42px; border-left: 2px solid var(--line);
  margin-left: 13px;
}
.primer-uso .pasos > li:last-child { border-left-color: transparent; padding-bottom: var(--s2); }
.primer-uso .pasos > li::before {
  content: counter(paso);
  position: absolute; left: -14px; top: -2px;
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  background: var(--accent); color: var(--on-accent);
  font-size: var(--fs-2xs); font-weight: 700; font-variant-numeric: tabular-nums;
}
.primer-uso .pasos b { display: block; font-size: var(--fs-sm); line-height: 1.35; }
.primer-uso .pasos .muted { display: block; font-size: var(--fs-2xs); margin: 3px 0 8px; max-width: 62ch; }
.primer-uso .mientras { margin-top: var(--s3); padding-top: var(--s3); border-top: 1px solid var(--line); }
.primer-uso .mientras > b { font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: .4px; color: var(--muted); }
.primer-uso .mientras-l { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); margin-top: var(--s2); }
.primer-uso .mientras-l a {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 24px; font-size: var(--fs-xs);
}
.primer-uso .mientras-l svg { width: 15px; height: 15px; }

/* ── D-base · Tono semántico para TEXTO ───────────────────
   Contraparte de .badge-<tono> cuando el color va en el texto y no en un
   fondo. Usa los tokens `-ink`, que son los únicos del sistema pensados para
   leerse: los sólidos (--ok, --warn) son colores de acento —trazos, bordes,
   cifras enormes— y sobre blanco se quedan en 3,2:1.

   Antes las vistas ponían el hex de la banda directamente en `color:`, con
   dos consecuencias: en claro las cifras de 13-16 px no llegaban a 4,5:1, y
   en oscuro no cambiaban nada porque el hex venía fijo desde el servidor.
   Medido con los tokens: 7,1:1 en claro y 14:1 en oscuro. */
.tono-ok { color: var(--ok-ink); }
.tono-good { color: var(--good-ink); }
.tono-warn { color: var(--warn-ink); }
.tono-danger { color: var(--danger-ink); }
.tono-info { color: var(--info-ink); }
.tono-na { color: var(--muted); }

/* La cifra grande del hero (56 px) es «texto grande» para WCAG: le basta 3:1
   y ahí sí conviene el color sólido, que es más vivo. Se mantiene la pareja
   por tema para que el modo oscuro no quede con un verde apagado. */
.cifra-ok { color: var(--ok); }
.cifra-good { color: var(--good); }
.cifra-warn { color: var(--warn); }
.cifra-danger { color: var(--danger); }
.cifra-na { color: var(--muted); }

/* ── R39 · Repaso de la colección ─────────────────────────
   Lista de acción: texto a la izquierda, botón a la derecha, y en móvil el
   botón cae debajo en vez de estrujar el título. */
.repaso-lista { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--s2); }
.repaso-lista li {
  display: flex; gap: var(--s3); align-items: center; flex-wrap: wrap;
  padding: var(--s2) var(--s3); border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2); transition: opacity var(--t-fast);
}
.repaso-lista .rp-txt { flex: 1 1 220px; min-width: 0; }
.repaso-lista .rp-txt a { display: block; font-size: var(--fs-xs); font-weight: 600; }
.repaso-lista .rp-txt .muted { display: block; font-size: var(--fs-3xs); margin-top: 2px; }
.repaso-lista button { flex-shrink: 0; }
