/* =========================================================
   FILEY 2027 — hoja de estilos del sistema

   Tokens y componentes de prototipo/common/styles-base.css y
   prototipo/REG/styles.css. Es la hoja compartida por todo el
   monolito: los módulos que se agreguen (EVT, TAL, STD, VIS)
   reutilizan estos tokens y componentes en vez de traer los
   suyos, para que el sistema se vea como uno solo.
   ========================================================= */

:root {
  /* ---- Tokens canónicos primarios ---- */
  --color-blanco: #FFFFFF;
  --color-azul-link-enfoque: #C8D7E3;
  --color-azul-institucional: #01457C;
  --color-azul-texto: #00437C;
  --caret-svg: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 826 374' fill='%2300437C'%3E%3Cpath d='M731.872 6.864 412 318.672 92.128 6.864a30.592 30.592 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L389.664 380.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.592 30.592 0 0 0-42.752 0z'/%3E%3C/svg%3E");
  --color-azul-institucional-enfoque: #013763;
  --color-azul-degradado-claro: #01497B;
  --color-azul-degradado-oscuro: #151A30;
  --color-dorado-encabezado: #C99213;
  --color-dorado-700: #B3810F;
  --color-dorado-800: #9C700B;
  --color-dorado-degradado-oscuro: #6E4D03;
  --color-negro: #000000;
  --font-filey: "Open Sans", "Segoe UI", "Helvetica Neue", Arial, sans-serif;

  /* ---- Tokens canónicos secundarios ---- */
  --color-verde-lima: #99BA15;
  --color-verde-700: #839F10;
  --color-verde-800: #6C840B;
  --color-verde-degradado-oscuro: #3F4E00;
  /* Verde bosque: el acento de talleres (`filey-identidad` §1). Eran dos hex
     sueltos en `.banner-infantil` hasta el 05-10-2026. */
  --color-verde-bosque: #1d8a4e;
  --color-verde-bosque-oscuro: #0a6b53;
  --color-rojo: #CC311D;
  --color-rojo-700: #912111;
  /* Los dos entran con el calendario de CU-EVT-001, que necesita cinco
     colores distinguibles entre sí. Están en `prototipo/common` desde el
     principio con estos mismos valores; lo que hacían falta aquí. */
  --color-morado: #955FF2;
  --color-magenta-oscuro: #70124C;

  /* ---- Grises internos ---- */
  --tinta: #1b2330;
  --gris-700: #3f4a5a;
  --gris-500: #6b7686;
  --gris-300: #c8d0db;
  --gris-200: #e2e7ee;
  --gris-100: #eef1f6;
  --gris-050: #f7f9fc;

  /* ---- Estado ---- */
  --ok-600: #1d8a4e;
  --ok-200: #c9e5d3;
  --ok-050: #e7f6ee;
  --warn-600: #b8860b;
  --warn-050: #fdf6e3;
  --oro-200: #f1e3b0;
  --oro-050: #fbf6e6;
  --err-600: var(--color-rojo);
  --err-200: #eecac5;
  --err-050: #fbeae8;

  /* ---- Espaciado / forma ---- */
  --radio: 12px;
  --radio-sm: 8px;
  --sombra-sm: 0 1px 2px rgba(16, 36, 64, .08), 0 1px 3px rgba(16, 36, 64, .06);
  --sombra-md: 0 6px 18px rgba(16, 36, 64, .10);
  --sombra-lg: 0 18px 50px rgba(16, 36, 64, .18);
  --maxw: 1120px;

  /* ---- Azules de apoyo ---- */
  --azul-600: #0a6fc2;
  --azul-300: #7fb3e0;
  --azul-100: #e6f0fa;
  --azul-050: #f3f8fd;
}

* { box-sizing: border-box; }

html { height: 100%; }

/* El pie se apoya en el fondo de la ventana cuando la pantalla no da
   para llenarla —una cuenta sin reserva, un listado vacío, un 403—, y
   baja con el contenido cuando sí. No es un pie fijo: sigue estando al
   final del documento, solo que el hueco sobrante se lo queda el
   contenido en vez de dejar la franja azul flotando a media pantalla
   con gris debajo.

   `body` en columna flexible y `min-height` contra la altura de `html`
   —no `100vh`—: en los navegadores con barra de direcciones que se
   esconde, `100vh` es más alto que lo visible y deja un hueco. */
body {
  margin: 0;
  min-height: 100%;
  display: flex;
  flex-direction: column;
  font-family: var(--font-filey);
  color: var(--tinta);
  background: var(--gris-050);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--azul-600); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Alpine tarda unos milisegundos en arrancar: sin esto, lo que debe
   estar oculto al cargar aparece y desaparece de un parpadeo. */
[x-cloak] { display: none !important; }

/* `hidden` tiene que esconder **siempre**, y por eso lleva `!important`:
   el navegador lo aplica con `display: none` de su hoja de estilos, que
   pierde contra cualquier `display` nuestro —`.btn` es `inline-flex`, y
   un botón escondido se seguía viendo—.

   Es lo que sostiene el patrón de esconder con el servidor y descubrir
   con Alpine: la plantilla pinta `hidden` según el estado inicial y
   Alpine quita el atributo con `:hidden`. `x-cloak` no sirve para eso
   —si Alpine no llega, esconde para siempre— y `x-show` tampoco —si no
   llega, no esconde nada—. */
[hidden] { display: none !important; }

/* ===================== Top bar ===================== */
.topbar {
  background: var(--color-azul-institucional);
  color: var(--color-blanco);
  border-bottom: 3px solid var(--color-dorado-encabezado);
  position: sticky;
  top: 0;
  z-index: 20;
}
.topbar-inner {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 12px 24px;
  display: flex;
  align-items: center;
  gap: 16px;
}
/* `min-width: 0` no es defensivo: un ítem flexible no baja de su
   contenido mínimo sin él, y con el nombre de la feria dentro eso
   bastaba para empujar la barra fuera de la ventana. Con esto, la marca
   cede sitio y su texto se recorta. */
.home { display: flex; align-items: center; gap: 12px; min-width: 0; }
a.home { text-decoration: none; color: inherit; cursor: pointer; }
a.home:hover { opacity: 0.88; }
.home .logo { height: 34px; width: auto; display: block; filter: brightness(0) invert(1); }
.brand-text { display: flex; flex-direction: column; line-height: 1.1; min-width: 0; }
.brand-text strong, .brand-text small {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.brand-text strong { font-size: 17px; letter-spacing: .3px; }
.brand-text small { font-size: 11.5px; color: rgba(255,255,255,.78); text-transform: uppercase; letter-spacing: 1.5px; }

/* `flex-shrink: 0`: la marca cede, el menú no. Al revés, lo primero que
   se recortaría es el control con el que se cierra sesión. */
.topnav { margin-left: auto; display: flex; align-items: center; gap: 22px; flex: 0 0 auto; }
.topnav a { color: var(--color-blanco); font-size: 14px; }
.topnav a:hover, .topnav a:focus-visible { color: var(--color-azul-link-enfoque); text-decoration: none; }
/* El cierre de sesión es un formulario (POST): que no ocupe línea propia. */
.topnav form { margin: 0; display: flex; }

/* `.user-chip` vivía aquí y se retiró el 10-09-2026 con la barra vieja:
   era el nombre suelto al lado de «Cerrar sesión», y ahora el nombre es
   el disparador del menú (`.menu-cuenta__disparador`, más abajo). El
   avatar sí sigue, porque es lo que queda del chip en pantalla
   estrecha. */
.avatar {
  width: 30px; height: 30px; border-radius: 50%;
  background: var(--color-dorado-encabezado); color: var(--color-azul-institucional-enfoque);
  display: grid; place-items: center; font-weight: 700; font-size: 13px;
}

.topbar.is-admin { background: var(--color-azul-institucional-enfoque); }
.avatar.is-admin { background: var(--color-dorado-encabezado); color: var(--color-blanco); }

/* La barra no tuvo ni una media query hasta el 10-09-2026, y por eso se
   desbordaba: de 919px para abajo se servía el mismo renglón rígido.
   Recoger las acciones en el menú quitó la mayor parte del ancho; lo que
   queda es apretar las dos piezas que siguen.

   **El alto no cambia**, y es deliberado: el logo sigue midiendo 34px y
   el relleno sigue siendo de 12px, así que `--alto-topbar` —de la que
   cuelgan `.admin-side`, `.panel-dictamen` y dos `scroll-margin-top`—
   sigue diciendo la verdad en un teléfono. Cualquier cambio de aquí que
   toque el alto obliga a redeclarar esa variable dentro de este bloque. */
@media (max-width: 720px) {
  .topbar-inner { padding: 12px 16px; gap: 12px; }

  /* El nombre se va del disparador y se queda en la ficha del panel:
     tres campos de nombre no caben al lado de una marca. Quedan las
     iniciales, que es lo que el avatar ya decía. */
  .menu-cuenta__nombre { display: none; }
  .menu-cuenta__disparador { padding: 6px 10px 6px 6px; }

  /* El subtítulo va en mayúsculas y con `letter-spacing`, así que es la
     línea más ancha de la marca y la que menos aporta: «Panel de
     administración» ya lo dice el color de la barra. */
  .brand-text small { display: none; }
  .brand-text strong { font-size: 15.5px; }
}

/* Chip de rol (panel admin) */
.rol-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: rgba(201,146,19,.20); border: 1px solid rgba(201,146,19,.40);
  padding: 5px 12px; border-radius: 999px;
  font-size: 13px; color: var(--color-dorado-encabezado); font-weight: 700;
}

/* ═══ El menú de la cuenta ═════════════════════════════════════
   Recoge las tres acciones que antes iban sueltas por la barra: ver
   ferias, cambiar de cara y cerrar sesión. Sustituye a `.user-chip`
   como disparador, y con él se fueron `.btn-salir` y `.btn-modo`.

   Es un `<details>`, así que **abre sin JavaScript**. Ver el comentario
   de `componentes/topbar.html`: aquí dentro está cerrar sesión, y eso
   no puede depender de que Alpine cargue. */

.menu-cuenta { position: relative; }

/* El marcador nativo del `<summary>` sobra —hay una flecha propia— y
   hay que quitarlo por los dos caminos: `list-style` para los
   navegadores al día y el pseudoelemento para WebKit. */
.menu-cuenta > summary { list-style: none; }
.menu-cuenta > summary::-webkit-details-marker { display: none; }

.menu-cuenta__disparador {
  display: flex; align-items: center; gap: 10px;
  background: rgba(255,255,255,.10);
  border: 1px solid rgba(255,255,255,.18);
  padding: 6px 12px 6px 6px;
  border-radius: 999px;
  font-size: 13.5px; color: var(--color-blanco);
  cursor: pointer; user-select: none;
}
.menu-cuenta__disparador:hover { background: rgba(255,255,255,.16); }
.menu-cuenta__disparador:focus-visible {
  outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px;
}

/* La flecha, dibujada con bordes para no cargar un icono por una punta
   de siete píxeles. Gira al abrir, que es la única señal de estado que
   da un `<details>` sin ayuda. */
.menu-cuenta__flecha {
  width: 0; height: 0; flex: 0 0 auto;
  border-left: 4px solid transparent; border-right: 4px solid transparent;
  border-top: 5px solid rgba(255,255,255,.7);
  transition: transform .15s ease;
}
.menu-cuenta[open] .menu-cuenta__flecha { transform: rotate(180deg); }

.menu-cuenta__panel {
  position: absolute; top: calc(100% + 10px); right: 0;
  min-width: 232px; max-width: calc(100vw - 32px);
  background: var(--color-blanco);
  border: 1px solid var(--gris-200); border-radius: var(--radio);
  box-shadow: var(--sombra-lg);
  padding: 6px; z-index: 1;
  display: flex; flex-direction: column;
}
.menu-cuenta__panel form { margin: 0; display: block; }

/* Quién es quien está dentro. En pantalla estrecha el disparador se
   queda en las iniciales, así que esta ficha es lo único que dice con
   qué cuenta se está mirando. */
.menu-cuenta__ficha {
  display: flex; flex-direction: column; gap: 2px;
  padding: 10px 12px 12px; margin-bottom: 4px;
  border-bottom: 1px solid var(--gris-200);
}
.menu-cuenta__ficha strong { font-size: 14px; color: var(--tinta); }
.menu-cuenta__ficha small {
  font-size: 12.5px; color: var(--gris-500);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

/* Las tres opciones se ven igual midan lo que midan: un enlace, un
   botón de formulario y otro botón de formulario tienen tres cajas por
   omisión, y en una lista eso se nota. */
.menu-cuenta__opcion {
  display: block; width: 100%; text-align: left;
  background: none; border: none; border-radius: var(--radio-sm);
  font-family: inherit; font-size: 14px; color: var(--gris-700);
  padding: 9px 12px; cursor: pointer; text-decoration: none;
}
.menu-cuenta__opcion:hover {
  background: var(--gris-100); color: var(--tinta); text-decoration: none;
}
.menu-cuenta__opcion:focus-visible {
  outline: 2px solid var(--color-azul-institucional); outline-offset: -2px;
}
/* Salir se separa de las demás: es la única que termina la sesión, y
   una lista donde todo pesa igual invita a pulsarla por inercia. */
.menu-cuenta__opcion.es-salir {
  margin-top: 4px; border-top: 1px solid var(--gris-200);
  border-radius: 0 0 var(--radio-sm) var(--radio-sm);
  color: var(--err-600);
}
.menu-cuenta__opcion.es-salir:hover { background: var(--err-050); color: var(--err-600); }

/* ===================== Layout ===================== */
.page { max-width: var(--maxw); margin: 0 auto; padding: 28px 24px 64px; }
/* El contenido crece hasta empujar el pie abajo; `0 auto` para que no
   encoja por debajo de lo que ocupa cuando sí hay de sobra.

   `width: 100%` es obligatorio, no defensivo: `.page` centra con
   `margin: 0 auto`, y un margen automático en el eje transversal
   **anula el estirado** de un ítem flexible. Sin esto la columna se
   encoge al ancho de su contenido y `--maxw` deja de mandar. */
main.page { flex: 1 0 auto; width: 100%; }

.page-head { margin-bottom: 22px; }
.page-head h1 { margin: 0 0 6px; font-size: 26px; color: var(--color-azul-institucional-enfoque); }
.page-head p { margin: 0; color: var(--gris-500); }

/* Páginas sueltas de mensaje (403, 404) */
.pagina-mensaje { max-width: 620px; padding-top: 64px; }

/* ===================== Auth ===================== */
.auth-wrap {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.05fr .95fr;
}
.auth-hero {
  background:
    linear-gradient(160deg, var(--color-azul-degradado-claro), var(--color-azul-degradado-oscuro)),
    radial-gradient(circle at 80% 10%, rgba(201,162,39,.35), transparent 45%);
  color: var(--color-blanco);
  padding: 56px 56px;
  display: flex; flex-direction: column; justify-content: center;
}
.auth-hero .logo-lg {
  height: 96px; width: auto; display: block; margin-bottom: 26px;
  filter: brightness(0) invert(1); align-self: flex-start;
}
.auth-hero h2 { font-size: 34px; line-height: 1.15; margin: 0 0 14px; max-width: 13ch; }
.auth-hero p { font-size: 16px; color: rgba(255,255,255,.78); max-width: 42ch; }
.auth-hero .pills { margin-top: 30px; display: flex; gap: 10px; flex-wrap: wrap; }
.auth-hero .pills span {
  background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.2);
  padding: 7px 13px; border-radius: 999px; font-size: 13px;
}

/* Variante admin: fondo dorado, identidad azul (REG/styles.css) */
.auth-hero.is-admin {
  background: linear-gradient(125deg, var(--color-dorado-encabezado) 0%, var(--color-dorado-degradado-oscuro) 100%);
  color: var(--color-azul-institucional-enfoque);
}
.auth-hero.is-admin .logo-lg { filter: none; }
.auth-hero.is-admin p { color: var(--color-azul-institucional); opacity: 0.9; }
.auth-hero.is-admin .pills span {
  background: rgba(255,255,255,.55);
  border-color: rgba(255,255,255,.85);
  color: var(--color-azul-institucional-enfoque);
  font-weight: 600;
}

.auth-form {
  background: var(--color-blanco);
  padding: 56px 56px;
  display: flex; flex-direction: column; justify-content: center;
}
.auth-card { width: 100%; max-width: 380px; margin: 0 auto; }
.auth-card h3 { margin: 0 0 6px; font-size: 22px; color: var(--color-azul-institucional-enfoque); }
.auth-card .sub { color: var(--gris-500); margin: 0 0 26px; font-size: 14.5px; }
.auth-foot { margin-top: 22px; font-size: 13.5px; color: var(--gris-500); text-align: center; }

/* OTP boxes */
.otp-row { display: flex; gap: 10px; justify-content: space-between; margin: 8px 0 4px; }
.otp-row input {
  width: 100%; aspect-ratio: 1 / 1; text-align: center;
  font-size: 26px; font-weight: 700; color: var(--color-azul-institucional-enfoque);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  font-family: inherit; padding: 0; min-width: 0;
}
.otp-row input:focus { outline: none; border-color: var(--azul-600); box-shadow: 0 0 0 3px var(--azul-100); }
.otp-meta { display: flex; justify-content: space-between; align-items: center; margin-top: 14px; font-size: 13px; color: var(--gris-500); }

.otp-estado {
  min-height: 18px;
  margin: 10px 0 0;
  font-size: 13px;
  text-align: center;
  color: var(--gris-500);
}
.otp-estado.err { color: var(--err-600); }

.otp-row.error input {
  border-color: var(--err-600);
  animation: otp-sacudir .3s ease;
}
@keyframes otp-sacudir {
  20%  { transform: translateX(-4px); }
  45%  { transform: translateX(4px); }
  70%  { transform: translateX(-2px); }
  100% { transform: none; }
}

.otp-cooldown {
  font-variant-numeric: tabular-nums;
  color: var(--gris-500);
}

/* ===================== Forms ===================== */
.field { margin-bottom: 16px; }
.field > label { display: block; font-weight: 600; font-size: 14px; margin-bottom: 6px; color: var(--gris-700); }
.field .hint { font-weight: 400; color: var(--gris-500); font-size: 12.5px; }
/* Dentro de un `<label>` la ayuda va en línea, junto al nombre del campo;
   suelta bajo el control tiene que ocupar su propio renglón. Sin esto se
   pega al lado del input y se lee como si fuera parte de él. */
.field > .hint { display: block; margin-top: 6px; line-height: 1.45; }
/* `.field > .hint` fija `display` y le gana al `hidden` del navegador
   (`filey-ui-componentes` §6): una ayuda escondida se pintaría igual. */
.field > .hint[hidden] { display: none; }
.adjunto-guardado strong { color: var(--tinta); font-weight: 600; }
.req { color: var(--color-rojo); font-weight: 700; }
.opt { color: var(--gris-500); font-weight: 400; font-size: 12.5px; }

/* Todos los controles de texto, con la misma caja.
   (Deriva corregida el 2026-08-29 — esta hoja iba **detrás** del prototipo.)
   `prototipo/common/styles-base.css` los tenía así desde el principio;
   esta copia perdió `number` y `textarea` al escribirse a mano, y con
   ellos toda la caja: un `<input type="number">` sin regla cae en el
   estilo de fábrica del navegador y sale angosto y con borde de pelo al
   lado de uno de texto. Se veía en la configuración de la convocatoria
   —costo por m² y porcentajes diminutos junto a la CLABE— y en la ficha
   de expositor, que también pide números.

   `date` y `search` son los añadidos de esta hoja: el prototipo no
   tiene ninguno —Angular traía su propio calendario y sus filtros— y
   aquí son los campos nativos de la configuración de la convocatoria
   (`fecha_limite_pronto_pago`) y de los buscadores del panel, que se
   veían como una caja de sistema al lado de su `<select>`.

   Se enumeran los tipos en vez de negar los otros porque los que quedan
   fuera **necesitan** su propio estilo: `checkbox` y `radio` no son
   cajas, y `file` tiene su regla más abajo. La lista es la de los tipos
   en uso; el día que entre uno nuevo, se añade aquí. */
input[type="text"], input[type="email"], input[type="tel"],
input[type="number"], input[type="date"], input[type="time"], input[type="search"],
select, textarea {
  width: 100%; padding: 11px 13px; font-size: 14.5px; color: var(--tinta);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  background: var(--color-blanco); font-family: inherit;
}
/* Crece hacia abajo y nunca a lo ancho: redimensionar en horizontal
   rompe la rejilla de la pantalla. */
textarea { resize: vertical; min-height: 80px; }

/* La casilla que confirma algo irreversible (CU-STD-035 A1). No es un
   campo de una ficha: es un pestillo. Por eso la etiqueta va **al lado**
   y no encima —la frase se lee antes de marcarla— y toda ella es el
   blanco del clic. */
.confirmacion {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 4px 0 14px; cursor: pointer; font-size: 14px; color: var(--tinta);
}
.confirmacion input[type="checkbox"] {
  flex: 0 0 auto; width: 17px; height: 17px; margin-top: 1px;
  accent-color: var(--color-azul-institucional); cursor: pointer;
}
select {
  -webkit-appearance: none;
  appearance: none;
  padding-right: 34px;
  cursor: pointer;
}

/* Wrapper del select: pone el caret con ::after y mask-image. El caret
   es un token embebido como data-URI y no un `url()` a un .svg suelto,
   porque el prototipo se abre con file:// y ahí un archivo externo no
   carga (ver el skill `filey-ui-componentes`, §6.4). */
.select-wrap {
  position: relative;
  display: block;
}
.select-wrap select { width: 100%; }
.select-wrap::after {
  content: '';
  position: absolute;
  right: 12px;
  top: 50%;
  pointer-events: none;
  width: 11px;
  height: 6px;
  background-color: var(--color-azul-texto);
  -webkit-mask-image: var(--caret-svg);
  mask-image: var(--caret-svg);
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
  transform: translateY(-50%) rotate(180deg);
  transition: background-color 0.2s ease, transform 0.2s ease;
}
.select-wrap:hover::after {
  background-color: var(--color-dorado-encabezado);
}

input:focus, select:focus, textarea:focus {
  outline: none; border-color: var(--azul-600); box-shadow: 0 0 0 3px var(--azul-100);
}
input::placeholder, textarea::placeholder { color: #aab3c0; }
input[readonly] { background: var(--gris-100); color: var(--gris-500); cursor: not-allowed; }

/* El campo que falló se ve de un vistazo: borde rojo y un halo tenue,
   porque solo el borde se pierde entre treinta cajas iguales. El color
   no va solo — el control lleva `aria-invalid` y su mensaje va ligado
   con `aria-describedby`, para quien no lo distingue o no lo ve. */
.field input.is-invalid,
.field select.is-invalid,
.field textarea.is-invalid {
  border-color: var(--err-600);
  box-shadow: 0 0 0 3px var(--err-050);
}
.field input.is-invalid:focus,
.field select.is-invalid:focus,
.field textarea.is-invalid:focus { outline-color: var(--err-600); }

/* El hueco del mensaje existe siempre y se colapsa vacío: un nodo que
   aparece de la nada empuja el resto del formulario hacia abajo justo
   cuando alguien está leyendo. */
.campo-error:empty { display: none; }
.campo-error { display: block; margin-top: 4px; }

/* El mensaje va en rojo, y hace falta decirlo aquí **con tres clases**.

   El marcado es `<span class="hint req campo-error">`: `.req` ya pinta
   de rojo, pero `.field .hint` (dos clases) le gana por especificidad y
   lo devolvía al gris de una ayuda. El error salía del mismo color y
   tamaño que el «Como aparece en tu constancia de situación fiscal.» de
   dos renglones más arriba, y en una ficha de treinta campos eso es un
   error que no se ve.

   Fuera de un `.field` no pasaba —ahí `.req` no tiene rival y el aviso
   de las bases siempre salió rojo—, que es lo que lo mantuvo escondido.

   El peso va con el color y no en su lugar: lo que separa un error de
   una ayuda tiene que sobrevivir a una captura en blanco y negro
   (`filey-identidad` §4). */
.field .hint.campo-error { color: var(--err-600); font-weight: 600; }

.msg-error {
  margin: 6px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--err-600);
}

/* Botón con aspecto de enlace (reenviar código). */
.enlace {
  background: none; border: none; padding: 0; font: inherit;
  color: var(--azul-600); cursor: pointer; text-decoration: none;
}
.enlace:hover { text-decoration: underline; }

/* ===================== Botones ===================== */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  border: none; border-radius: var(--radio-sm); padding: 12px 20px;
  font-size: 14.5px; font-weight: 600; cursor: pointer; font-family: inherit;
  transition: transform .04s ease, box-shadow .15s ease, background .15s ease;
}
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--color-azul-institucional); color: var(--color-blanco); box-shadow: var(--sombra-sm); }
.btn-primary:hover, .btn-primary:focus-visible { background: var(--color-azul-institucional-enfoque); text-decoration: none; }
.btn-gold { background: var(--color-dorado-encabezado); color: var(--color-azul-institucional-enfoque); }
.btn-gold:hover { background: var(--color-dorado-700); text-decoration: none; }
.btn-ghost { background: var(--color-blanco); color: var(--azul-600); border: 1.5px solid var(--gris-300); }
.btn-ghost:hover { border-color: var(--azul-600); background: var(--azul-050); text-decoration: none; }
.btn-block { width: 100%; }
.btn-lg { padding: 14px 24px; font-size: 15.5px; }

.btn:disabled {
  background: var(--gris-200);
  color: var(--gris-500);
  cursor: not-allowed;
  box-shadow: none;
}
.btn:disabled:active { transform: none; }

/* Botón "Próximamente" (tarjeta TAL del prototipo) */
.btn-proximamente {
  background: var(--azul-050);
  border: 1.5px solid var(--azul-300);
  color: var(--azul-600);
  pointer-events: none;
  cursor: default;
}

/* Estado "enviando…" de los botones.
   HTMX pone la clase `htmx-request` en el formulario mientras la
   petición está en vuelo; con eso basta para intercambiar las dos
   etiquetas del botón sin una línea de código propio. */
.etiqueta-cargando { display: none; }
.htmx-request .etiqueta-lista { display: none; }
.htmx-request .etiqueta-cargando { display: inline; }

/* ===================== Convocatorias (cards) ===================== */
.conv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 22px; }
.conv-card {
  background: var(--color-blanco); border: 1px solid var(--gris-200); border-radius: var(--radio);
  overflow: hidden; box-shadow: var(--sombra-sm); display: flex; flex-direction: column;
  transition: box-shadow .18s ease, transform .18s ease;
}
.conv-card:hover { box-shadow: var(--sombra-md); transform: translateY(-3px); }
.conv-card.is-closed { opacity: .92; }
/* Las tarjetas de una fila comparten el alto del banner, cuerpo y pie. */
@supports (grid-template-rows: subgrid) {
  .conv-grid > .conv-card {
    display: grid; grid-row: span 3; grid-template-rows: subgrid; row-gap: 0;
  }
}
.conv-banner {
  /* El nombre completo determina el alto; el icono tiene su propia franja. */
  min-height: 116px; flex-shrink: 0; display: flex; align-items: flex-end; padding: 54px 18px 14px;
  color: var(--color-blanco); position: relative;
}
.conv-banner .ico { font-size: 30px; line-height: 1; position: absolute; top: 14px; right: 16px; opacity: .85; }
.conv-banner h3 { margin: 0; font-size: 19px; min-width: 0; overflow-wrap: anywhere; }
/* La fórmula del acento (`comun/acentos.py`). Cada `.banner-*` fija
   **un solo color**, `--acento`, y los dos degradados salen de él igual
   para todos: el color mezclado con negro en `oklch`, que oscurece sin
   cambiar el tono. La tarjeta va del 100 % al 65 %; la portada de la
   información (`.info-hero`), del 100 % al 60 %. Un
   acento nuevo es una línea aquí y una fila clasificada en `acentos.py`;
   sin acento, el dorado. */
.banner-stand    { --acento: var(--color-azul-degradado-claro); }
.banner-infantil { --acento: var(--color-verde-bosque); }
.banner-eventos  { --acento: var(--color-dorado-encabezado); }
.banner-visitas  { --acento: var(--color-verde-700); }
.banner-morado   { --acento: var(--color-morado); }
.banner-magenta  { --acento: var(--color-magenta-oscuro); }
.conv-banner {
  background: linear-gradient(135deg,
    var(--acento, var(--color-dorado-encabezado)),
    color-mix(in oklch, var(--acento, var(--color-dorado-encabezado)) 65%, var(--color-negro)));
}
.conv-body { padding: 18px; flex: 1; display: flex; flex-direction: column; gap: 12px; }
.conv-body p { margin: 0; font-size: 13.5px; color: var(--gris-700); flex: 1; }
.conv-dates { font-size: 12.5px; color: var(--gris-500); display: flex; align-items: center; gap: 6px; }
.conv-foot { padding: 0 18px 18px; }

.badge {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; font-weight: 700; padding: 4px 11px; border-radius: 999px;
  text-transform: uppercase; letter-spacing: .5px;
}
.badge::before { content: ""; width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
.badge-open   { background: var(--ok-050);   color: var(--ok-600); }
.badge-closed { background: var(--gris-100); color: var(--gris-500); }
.badge-soon   { background: var(--warn-050); color: var(--warn-600); }
.badge-pending  { background: var(--azul-100); color: var(--azul-600); }
.badge-accepted { background: var(--ok-050);   color: var(--ok-600); }
.badge-rejected { background: var(--err-050);  color: var(--err-600); }
.badge-changes  { background: var(--warn-050); color: var(--warn-600); }

/* ===================== Notas / avisos ===================== */
.note {
  display: flex; gap: 12px; padding: 14px 16px; border-radius: var(--radio-sm);
  font-size: 13.5px; margin-bottom: 18px;
}
.note .ico { flex: 0 0 22px; font-size: 18px; line-height: 1.2; }
.note-info { background: var(--azul-050); color: var(--color-azul-institucional); border: 1px solid var(--azul-100); }
/* El contacto que cierra las pantallas del expositor de STD: separado del
   contenido y pegado al pie, que ya trae su propio margen. */
.nota-contacto {
  margin: 32px 0 0; padding: 16px 20px; font-size: 14.5px;
  border-left: 4px solid var(--color-azul-institucional);
}
.nota-contacto a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
/* El correo resaltado: en su renglón, más grande y en negritas. Con dos
   clases para ganarle a `.nota-contacto a`, que hereda el color. */
.nota-contacto .nota-contacto__correo {
  display: block; width: fit-content; margin-top: 4px;
  font-size: 17px; font-weight: 700; color: var(--color-azul-institucional);
}
/* Los espacios de la reserva, bajo el plano de la cuenta: el rótulo y los
   distintivos en una fila que salta de renglón si no caben. */
.mapa-mis-stands {
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
  margin: 14px 0 0; font-size: 14px; color: var(--gris-700);
}
.mapa-mis-stands strong { margin-right: 4px; }
.note-ok   { background: var(--ok-050); color: var(--ok-600); border: 1px solid #cfe6d5; }
.note-err  { background: var(--err-050); color: var(--err-600); border: 1px solid #eecac5; }
.note-warn { background: var(--warn-050); color: #7a5a00; border: 1px solid var(--oro-200); }

/* Una lista dentro de un aviso. Hereda su color, y sangra con la viñeta
   dentro del margen para no perder ancho en una columna estrecha. */
.note-lista { margin: 8px 0; padding-left: 18px; display: grid; gap: 5px; }
.note-lista li { line-height: 1.45; }
.note-gold { background: var(--oro-050); color: var(--color-dorado-700); border: 1px solid var(--oro-200); }

.closed-overlay {
  background: var(--gris-100); color: var(--gris-500);
  text-align: center; padding: 10px; font-size: 13px; font-weight: 600;
  border-radius: var(--radio-sm);
}

/* ===================== Avisos flotantes ===================== */
/* Reemplazan al MatSnackBar de Angular. Se alimentan de
   django.contrib.messages y del store `avisos` de Alpine. */
/* Abajo a la derecha, y no centrado: en el centro tapaba los botones de
   guardar, que es justo lo siguiente que se quiere pulsar. Se enseña uno a
   la vez —los que esperan se cuentan dentro— y se cierra a mano. */
.avisos {
  position: fixed; right: 24px; bottom: 24px;
  z-index: 60; display: flex; flex-direction: column; gap: 10px;
  width: min(420px, calc(100vw - 32px));
}
.aviso {
  display: flex; align-items: flex-start; gap: 14px;
  background: var(--color-azul-degradado-oscuro); color: var(--color-blanco);
  padding: 14px 18px; border-radius: var(--radio-sm);
  box-shadow: var(--sombra-lg); font-size: 14px;
}
.aviso span { flex: 1; }
.aviso-texto { flex: 1; display: grid; gap: 4px; }
/* Cuántos vienen detrás. Va dentro del aviso y no como una pila de
   avisos: la cuenta ocupa un renglón y la pila, media pantalla. */
.aviso-fila { font-size: 12px; opacity: .75; }
.aviso-error { background: var(--err-600); }
.aviso-success { background: var(--ok-600); }
.aviso-warning { background: var(--warn-600); }
.aviso-cerrar {
  align-self: flex-start;
  background: none; border: none; color: inherit; cursor: pointer;
  font-size: 20px; line-height: 1; padding: 0 2px; opacity: .8;
}
.aviso-cerrar:hover { opacity: 1; }

/* ===================== Footer ===================== */
.footer {
  flex: 0 0 auto;
  background: var(--color-azul-institucional); color: rgba(255,255,255,.78); margin-top: 40px;
  border-top: 3px solid var(--color-dorado-encabezado);
}
.footer-inner { max-width: var(--maxw); margin: 0 auto; padding: 28px 24px; display: flex; justify-content: space-between; gap: 20px; flex-wrap: wrap; font-size: 13px; }
.footer strong { color: var(--color-blanco); }

/* ===================== Responsive ===================== */
@media (max-width: 920px) {
  .auth-wrap { grid-template-columns: 1fr; }
  .auth-hero { display: none; }
}

/* =========================================================
   Panel de administración de una feria (FER)
   Todo lo de abajo es copia literal del prototipo —mismos nombres de
   clase y de token—, traída al usarse por primera vez en Django. Ver
   `filey-render` §6: mientras las dos hojas existan, lo que se porta se
   copia tal cual; inventar un nombre distinto para el mismo valor es lo
   que las vuelve irreconciliables.
   ========================================================= */

/* .page-narrow · prototipo/common/styles-base.css */
.page-narrow { max-width: 760px; }

/* .card / .section-card · prototipo/common/styles-base.css */
.card {
  background: var(--color-blanco); border: 1px solid var(--gris-200);
  border-radius: var(--radio); box-shadow: var(--sombra-sm);
}
.card-pad { padding: 24px 26px; }

.section-card { margin-bottom: 22px; }
.section-card > .section-head {
  display: flex; align-items: center; gap: 12px;
  padding: 16px 24px; border-bottom: 1px solid var(--gris-200);
  background: var(--gris-050); border-radius: var(--radio) var(--radio) 0 0;
}
.section-card > .section-head .num {
  width: 28px; height: 28px; border-radius: 50%; flex: 0 0 28px;
  background: var(--color-azul-institucional); color: var(--color-blanco);
  display: grid; place-items: center; font-weight: 700; font-size: 14px;
}
.section-card > .section-head h3 { margin: 0; font-size: 16.5px; color: var(--color-azul-institucional-enfoque); }
.section-card > .section-head p { margin: 2px 0 0; font-size: 13px; color: var(--gris-500); }
.section-card > .section-body { padding: 22px 24px; }

/* La barra superior es fija, así que traer una sección «al principio de
   la vista» la deja **debajo** de ella y su encabezado no se ve.
   `scroll-margin-top` reserva ese hueco: lo respetan tanto
   `scrollIntoView` como una navegación por ancla, y no hay que calcular
   nada a mano. */
#campos-tipo { scroll-margin-top: calc(var(--alto-topbar) + 16px); }

/* .table · prototipo/common/styles-base.css */
.table { width: 100%; border-collapse: collapse; background: var(--color-blanco); }
.table th, .table td { text-align: left; padding: 14px 16px; font-size: 14px; border-bottom: 1px solid var(--gris-200); }
.table th { background: var(--gris-050); color: var(--gris-700); font-size: 12.5px; text-transform: uppercase; letter-spacing: .5px; }
.table tr:hover td { background: var(--azul-050); }
/* El folio, destacado dentro de la tabla. Se trae de
   `prototipo/common/styles-base.css` con el mismo nombre y los mismos
   valores al portar la cola de propuestas de EVT, que es la primera
   pantalla servida que lo enseña: es la columna con la que se nombra una
   propuesta en voz alta y en cualquier trámite posterior. */
.table .folio { font-weight: 700; color: var(--color-azul-institucional); }

/* .form-actions · prototipo/common/styles-base.css */
.form-actions {
  display: flex; gap: 12px; justify-content: flex-end; align-items: center;
  margin-top: 8px; padding-top: 20px; border-top: 1px solid var(--gris-200);
}
.form-actions .spacer { margin-right: auto; }
/* En una tarjeta de confirmación los botones van centrados y sin la
   línea de arriba: ahí no cierran un formulario, son el final de un
   mensaje. El prototipo lo resolvía con un `style=` en la etiqueta;
   al portarlo baja a la hoja con nombre propio. */
.form-actions-centrada { justify-content: center; border-top: none; margin-top: 18px; }

/* .toolbar-row y .btn-row · prototipo/EVT/styles.css */
.toolbar-row { display: flex; gap: 16px; align-items: center; justify-content: space-between; margin-bottom: 20px; flex-wrap: wrap; }
.btn-row { padding: 7px 14px; font-size: 13px; }

/* .btn-danger · prototipo/VIS/styles.css */
.btn-danger { background: var(--err-050); color: var(--err-600); border: 1.5px solid #eecac5; }
.btn-danger:hover { background: #f7dcd8; text-decoration: none; }

/* ── Portadas de STD (U1, A1, A2) ──────────────────────────────
   Copiado de prototipo/common/styles-base.css con los mismos nombres,
   como manda el skill `filey-render` §6: al portar una pantalla se trae
   solo lo que esa pantalla usa. */

/* .grid-2 · líneas 263 · formularios de dos columnas */
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0 18px; }

/* .radio-row · línea 266 · opciones en línea (giro, materiales) */
.radio-row { display: flex; gap: 22px; padding-top: 4px; flex-wrap: wrap; }
.radio-row label { display: flex; align-items: center; gap: 8px; font-weight: 500; font-size: 14.5px; color: var(--gris-700); }

/* .multi-input · línea 281 · lista de nombres (los sellos editoriales) */
.multi-input { display: flex; flex-direction: column; gap: 8px; }

/* .multi-fila · NUEVO, sin contraparte en el prototipo.
   Una fila de una lista editable: los campos crecen y la acción se queda
   del tamaño que necesita. La usan los sellos de STD (nombre + carta).
   STD no tiene pantallas en `prototipo/`, así que esto nace aquí; si el
   prototipo llega a tenerlas, hay que reconciliarlo. */
.multi-fila { display: flex; gap: 14px; align-items: flex-start; margin-bottom: 6px; }
/* `display: flex` le gana al `hidden` del navegador (`filey-ui-componentes`
   §6): las filas de sellos de más allá del total se esconden con él. */
.multi-fila[hidden] { display: none; }
.multi-fila > .field { flex: 1 1 0; margin-bottom: 14px; }

/* Alinear por arriba y no por abajo (`flex-end`) es lo que pone las dos
   etiquetas en el mismo renglón y los dos controles a la misma altura.
   Con `flex-end` mandaba el campo más alto —el de archivo, que además
   lleva su ayuda debajo— y el otro quedaba a media altura, como si
   fueran de dos filas distintas.
   La contrapartida: el botón, que no tiene etiqueta, sube demasiado. Se
   baja el alto exacto de un bloque de etiqueta para que quede a la
   altura de los controles. */
.multi-fila { --alto-etiqueta: 27px; }  /* 14px · line-height 1.5 + 6px */
.multi-fila > .btn { flex: 0 0 auto; margin-top: var(--alto-etiqueta); }

@media (max-width: 920px) {
  .multi-fila { flex-direction: column; align-items: stretch; }
  .multi-fila > .btn { margin-top: 0; align-self: flex-start; }
}

/* .info-grid · línea 382 · contenido principal + columna lateral */
.info-grid { display: grid; grid-template-columns: 1.6fr 1fr; gap: 24px; align-items: start; }

@media (max-width: 920px) {
  .grid-2, .info-grid { grid-template-columns: 1fr; }
}

/* .info-hero · prototipo/EVT/styles.css · el encabezado de una
   convocatoria. Es su portada: dice de qué va y si está abierta antes de
   que nadie lea una fecha. Su degradado sale del acento de su clase
   `.banner-*` con la fórmula de la tarjeta, del 100 % al 60 %. Empieza
   en el acento sin mezclar, así que su título (30 px, pide 3:1) queda
   justo en el dorado: 3.0:1 bajo la primera letra, en la portada más
   ancha. Lo mide `test_acentos.py`.

   El distintivo y el párrafo son texto pequeño (12 y 15 px), así que
   piden 4.5:1, y van sobre un **velo** que se lo asegura sin depender del
   degradado: blanco al 20 % o, con `--fondos-oscuros`, negro al 25 %.
   Cuál lleva cada acento lo dice `comun/acentos.py` (`Acento.fondo`). */
.info-hero {
  border-radius: var(--radio); overflow: hidden; margin-bottom: 24px;
  color: var(--color-blanco); padding: 34px 36px; position: relative;
  background: linear-gradient(135deg,
    color-mix(in oklch, var(--acento, var(--color-dorado-encabezado)) 100%, var(--color-negro)),
    color-mix(in oklch, var(--acento, var(--color-dorado-encabezado)) 60%, var(--color-negro)));
}
.info-hero h1 { margin: 0 0 8px; font-size: 30px; }
.info-hero p { margin: 0; max-width: 60ch; }
.info-hero .badge { margin-bottom: 14px; background: rgba(255, 255, 255, .2); color: var(--color-blanco); }
.info-hero .badge::before { background: var(--color-blanco); }
/* El velo del párrafo ocupa todo el ancho de la portada (menos su
   relleno) y el alto de lo que lleva dentro. */
.info-hero__texto { margin-top: 2px; padding: 10px 14px; border-radius: var(--radio-sm); background: rgba(255, 255, 255, .2); }
.info-hero--fondos-oscuros .badge,
.info-hero--fondos-oscuros .info-hero__texto { background: rgba(0, 0, 0, .25); }

/* .dl-dates · prototipo/EVT/styles.css · etiqueta a la izquierda y valor a
   la derecha, con filete punteado. Es una `<dl>` de verdad: cada fecha es
   la definición de un hito, no una fila de tabla. */
.dl-dates { display: grid; gap: 0; }
.dl-dates .row {
  display: flex; justify-content: space-between; gap: 16px;
  padding: 12px 0; border-bottom: 1px dashed var(--gris-200); font-size: 14px;
}
.dl-dates .row:last-child { border-bottom: none; }
.dl-dates .row dt { color: var(--gris-700); display: flex; align-items: center; gap: 9px; }
.dl-dates .row dd {
  margin: 0; font-weight: 700; color: var(--color-azul-institucional-enfoque);
  display: flex; flex-direction: column; align-items: flex-end; gap: 5px; text-align: right;
}
.dl-dates .row .dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 9px; background: var(--color-dorado-encabezado); }

/* La pastilla de estado de cada fecha. En el prototipo la pintaba un
   script sobre `db-evt.js`; aquí la decide el servidor comparando con hoy,
   que es un dato que ya tiene. */
.fecha-estado {
  font-size: 11px; font-weight: 700; letter-spacing: .2px; line-height: 1;
  padding: 3px 9px; border-radius: 999px; white-space: nowrap;
}
.fecha-estado.is-curso { background: var(--ok-050);   color: var(--ok-600); }
.fecha-estado.is-pend  { background: var(--azul-100); color: var(--azul-600); }
.fecha-estado.is-fin   { background: var(--err-050);  color: var(--err-600); }

/* ── Las ventanas de la convocatoria (CU-EVT-001) ───────────────────
   El calendario nació portado de `prototipo/EVT/styles.css` §Calendario
   estilo Outlook, como una rejilla fija al lado de las cajas; desde el
   02-10-2026 es un calendario emergente por caja (más abajo, `.evt-cal`).
   Lo que no cambió son los colores: en el prototipo cada hito tomaba
   prestado un color de estado —verde de «éxito» para la apertura, rojo
   de «error» para el cierre— y aquí las cinco ventanas son identidad, no
   estado, así que llevan tokens propios.

   Los cinco salen de la paleta de marca, ninguno es un hex suelto. Con
   `color-mix` cada ventana da su tinte claro para el relleno y su
   translúcido para las capas que se combinan cuando dos tramos se pisan,
   sin necesidad de diez tokens más. */
:root {
  --evt-ventana-recepcion: var(--color-azul-institucional);
  --evt-ventana-notificacion: var(--color-dorado-encabezado);
  --evt-ventana-ajustes: var(--color-morado);
  --evt-ventana-asignacion: var(--color-verde-700);
  --evt-ventana-constancias: var(--color-magenta-oscuro);
}

/* ── La sección Bases de la configuración (CU-EVT-001) ─────────────
   Dos bloques lado a lado —las fechas y «Otros»— bajo un título de
   sección. Las fechas llevan más ancho: son diez cajas y dos horas
   contra dos campos. A 920 px se apilan, primero las fechas. */
.evt-bases { margin-bottom: 4px; }
.evt-bases__titulo {
  margin: 0 0 14px; font-size: 20px; font-weight: 700;
  color: var(--color-azul-institucional-enfoque);
}
.evt-bases__rejilla {
  display: grid; grid-template-columns: minmax(0, 3fr) minmax(0, 2fr);
  gap: 22px; align-items: start;
}

/* El punto de color de cada ventana en el formulario y en «Así se lee
   hoy»: es lo que ata una caja con su color en el calendario emergente. */
.swatch { width: 12px; height: 12px; border-radius: 3px; flex: 0 0 12px; border: 1px solid var(--gris-200); }
.sw-recepcion { background: var(--evt-ventana-recepcion); }
.sw-notificacion { background: var(--evt-ventana-notificacion); }
.sw-ajustes { background: var(--evt-ventana-ajustes); }
.sw-asignacion { background: var(--evt-ventana-asignacion); }
.sw-constancias { background: var(--evt-ventana-constancias); }

/* Una ventana en el formulario: su rótulo con el color, y sus cajas.
   En la misma fila porque son una sola respuesta —cuándo pasa esto— y
   no dos preguntas. Tres columnas en todas, lleven hora o no, para que
   los «Desde» y los «Hasta» queden alineados de una ventana a otra. */
.ventana { padding: 12px 0; border: none; border-bottom: 1px dashed var(--gris-200); margin: 0; }
.ventana:last-child { border-bottom: none; }
.ventana__nombre {
  display: flex; align-items: center; flex-wrap: wrap; gap: 2px 8px;
  font-size: 13.5px; font-weight: 700;
  color: var(--color-azul-institucional-enfoque); margin-bottom: 8px;
}
.ventana__cajas { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }
.ventana__cajas .field { margin: 0; }
.ventana__cajas label { font-size: 12px; }
.ventana__cajas input:disabled { background: var(--gris-050); color: var(--gris-500); cursor: not-allowed; }

/* Una ventana que todavía no toca. Se apaga entera —no solo sus cajas—
   para que se lea como un paso que aún no llega y no como un campo roto,
   y el motivo se dice **una vez**, en la primera cerrada: las de más
   atrás esperan a ésa, así que repetirlo sería decir cuatro veces lo
   mismo.

   Sigue ocupando su sitio: esconderla movería el bloque entero cada vez
   que se captura una fecha, y lo que enseña es la forma completa del
   proceso. */
.ventana.bloqueada { opacity: .55; }
.ventana.bloqueada .ventana__nombre { color: var(--gris-500); }
.ventana { transition: opacity .18s ease; }
@media (prefers-reduced-motion: reduce) {
  .ventana { transition: none; }
}
/* El motivo va **en su propio renglón**, debajo del nombre: al lado lo
   partía en dos y la tarjeta cambiaba de alto al abrirse cada ventana. En
   minúscula y sin negrita, que es una aclaración y no un rótulo. */
.ventana__espera { flex-basis: 100%; font-weight: 400; color: var(--gris-500); }

/* Las cifras de la configuración en una fila mientras quepan. `auto-fit`
   las reparte sin media queries, y con una sola no deja hueco. */
.cifras {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 14px; align-items: end; margin-bottom: 18px;
}

/* La unidad, dentro de la caja y pegada a la derecha. No es un
   `placeholder` —ése se va al escribir— sino un rótulo fijo, y va a la
   derecha porque el valor se alinea a la izquierda: por mucho que crezca
   la cifra, el hueco reservado por `padding-right` impide que se toquen.

   `pointer-events: none` para que pulsar sobre la palabra siga entrando
   al campo, que es lo que espera quien apunta ahí. */
.con-unidad { position: relative; display: block; }
.con-unidad input { padding-right: 74px; }
.con-unidad .unidad {
  position: absolute; right: 12px; top: 50%; transform: translateY(-50%);
  font-size: 12.5px; color: var(--gris-500); pointer-events: none;
}
.cifras .field { margin: 0; }

/* ── El botón que sustituye a una caja de fecha o de hora ─────────────
   Lo pone `calendario-convocatoria.js` al lado de la caja nativa, que se
   esconde. Tiene la forma de una caja —mismo borde, alto y radio que un
   `input`— porque eso es lo que es para quien lo usa: el sitio donde va
   la fecha. El icono a la derecha dice qué se abre. */
.evt-selector {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 11px 12px 11px 13px; font: inherit; font-size: 14.5px; text-align: left;
  color: var(--tinta); background: var(--color-blanco);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm); cursor: pointer;
}
.evt-selector:hover { border-color: var(--azul-600); }
.evt-selector:focus-visible { outline: 2px solid var(--azul-600); outline-offset: 2px; }
.evt-selector__texto { flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.evt-selector__ico { flex: 0 0 auto; display: grid; color: var(--gris-500); }
.evt-selector__ico svg { display: block; width: 17px; height: 17px; }
.evt-selector.is-vacio .evt-selector__texto { color: var(--gris-500); }
.evt-selector:disabled { background: var(--gris-050); color: var(--gris-500); cursor: not-allowed; }
.evt-selector:disabled:hover { border-color: var(--gris-300); }
/* El reproche del servidor, que viene en la caja nativa y el guion copia
   aquí. Con su propio selector: el borde del componente va después de la
   regla genérica de `.is-invalid` y la taparía (`filey-ui-componentes` §6). */
.evt-selector.is-invalid { border-color: var(--err-600); }

/* ── La ventana emergente de los dos selectores ───────────────────────
   Una sola, colgada del `<body>` y con `position: fixed`, para que no la
   recorte ninguna tarjeta. El guion la coloca debajo del botón que la
   abrió, o encima si abajo no cabe. */
.evt-pop {
  position: fixed; z-index: 60; width: 336px; max-width: calc(100vw - 24px);
  background: var(--color-blanco); border: 1px solid var(--gris-200);
  border-radius: var(--radio); box-shadow: var(--sombra-lg); padding: 14px 14px 12px;
}
.evt-pop[hidden] { display: none; }
.evt-pop__rotulo { margin: 0 0 12px; font-size: 13px; font-weight: 600; color: var(--gris-700); }
.evt-pop__nota { margin: 10px 2px 0; font-size: 12.5px; color: var(--gris-500); }
.evt-pop__nota[hidden] { display: none; }
.evt-pop__pie {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--gris-200);
}
.evt-pop__hueco { flex: 1 1 auto; }

/* ── El calendario emergente ─────────────────────────────────────────
   Pequeño a propósito: no lleva leyendas escritas, solo colores, y el
   nombre de cada ventana sale al pasar por el día (`.evt-cal__aviso`). */
.evt-cal__cabeza { display: flex; align-items: center; gap: 6px; margin-bottom: 8px; }
/* El título es también la puerta a la rejilla de años: se ve como texto,
   pero con el borde al pasar dice que se puede pulsar. */
.evt-cal__titulo {
  margin-right: auto; padding: 6px 8px; font: inherit; font-size: 15px; font-weight: 700;
  color: var(--color-azul-institucional-enfoque); background: none;
  border: 1.5px solid transparent; border-radius: var(--radio-sm); cursor: pointer;
}
.evt-cal__titulo:hover { border-color: var(--gris-300); }
.evt-cal__nav {
  width: 32px; height: 32px; font-size: 18px; line-height: 1; color: var(--gris-700);
  background: var(--color-blanco); border: 1.5px solid var(--gris-300);
  border-radius: var(--radio-sm); cursor: pointer;
}
.evt-cal__nav:hover { border-color: var(--azul-600); color: var(--azul-600); }

/* Siete columnas sin hueco entre días: el tramo de una ventana tiene que
   leerse continuo de un día al siguiente. */
.evt-cal__rejilla { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); row-gap: 2px; }
.evt-cal__dow {
  text-align: center; font-size: 11px; font-weight: 700; color: var(--gris-500);
  padding: 4px 0 6px; letter-spacing: .5px;
}
.evt-cal__dia {
  height: 38px; padding: 0; font: inherit; font-size: 13.5px; color: var(--tinta);
  background: transparent; border: none; border-radius: 0; cursor: pointer; position: relative;
}
.evt-cal__dia:hover { box-shadow: inset 0 0 0 1.5px var(--azul-600); }
.evt-cal__dia:focus-visible { outline: 2px solid var(--azul-600); outline-offset: -2px; z-index: 1; }
.evt-cal__dia.es-vecino { color: var(--gris-500); opacity: .6; }
.evt-cal__dia.es-hoy { font-weight: 800; text-decoration: underline; text-underline-offset: 3px; }
/* Un día que la ventana no admite. Se apaga sin tachar: lo que cabe lo
   dice la línea del pie. */
.evt-cal__dia[aria-disabled="true"] { color: var(--gris-300); cursor: not-allowed; }
.evt-cal__dia[aria-disabled="true"]:hover { box-shadow: none; }

/* La ventana que se edita, en su color. El «desde» es el color inicial,
   el «hasta» el final y el tramo entre los dos, un tinte: los extremos
   redondean hacia fuera, así que se lee dónde empieza y dónde acaba. Los
   dos colores claros llevan `--tinta` encima, como sus recuadros del
   calendario anterior: con blanco no llegan a AA. */
.evt-cal__dia.v-recepcion { --ventana: var(--evt-ventana-recepcion); --ventana-texto: var(--color-blanco); }
.evt-cal__dia.v-notificacion { --ventana: var(--evt-ventana-notificacion); --ventana-texto: var(--tinta); }
.evt-cal__dia.v-ajustes { --ventana: var(--evt-ventana-ajustes); --ventana-texto: var(--color-blanco); }
.evt-cal__dia.v-asignacion { --ventana: var(--evt-ventana-asignacion); --ventana-texto: var(--tinta); }
.evt-cal__dia.v-constancias { --ventana: var(--evt-ventana-constancias); --ventana-texto: var(--color-blanco); }
.evt-cal__dia.es-propia.es-inicio,
.evt-cal__dia.es-propia.es-fin,
.evt-cal__dia.es-propia.es-unica { background: var(--ventana); color: var(--ventana-texto); font-weight: 700; }
.evt-cal__dia.es-propia.es-inicio { border-radius: var(--radio-sm) 0 0 var(--radio-sm); }
.evt-cal__dia.es-propia.es-fin { border-radius: 0 var(--radio-sm) var(--radio-sm) 0; }
.evt-cal__dia.es-propia.es-unica { border-radius: var(--radio-sm); }
.evt-cal__dia.es-propia.es-intermedio { background: color-mix(in srgb, var(--ventana) 22%, var(--color-blanco)); }
/* Lo elegido lleva además un aro: el color dice de qué ventana es y el
   aro dice que es lo que se va a aceptar. */
.evt-cal__dia.es-elegido { box-shadow: inset 0 0 0 2px var(--color-azul-institucional); }

/* Las rejillas de años y de meses, para saltar sin avanzar mes por mes. */
.evt-cal__rejilla-larga { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 6px; padding: 4px 0; }
.evt-cal__opcion {
  height: 44px; font: inherit; font-size: 14px; color: var(--tinta);
  background: var(--color-blanco); border: 1.5px solid var(--gris-200);
  border-radius: var(--radio-sm); cursor: pointer;
}
.evt-cal__opcion:hover { border-color: var(--azul-600); }
.evt-cal__opcion.es-actual { border-color: var(--color-azul-institucional); font-weight: 700; }
.evt-cal__opcion[aria-disabled="true"] { color: var(--gris-300); border-color: var(--gris-100); cursor: not-allowed; }

/* El nombre de las ventanas de un día, encima del día. */
.evt-cal__aviso {
  position: absolute; z-index: 2; display: grid; gap: 4px; max-width: 280px;
  padding: 7px 10px; font-size: 12px; color: var(--tinta);
  background: var(--color-blanco); border: 1px solid var(--gris-200);
  border-radius: var(--radio-sm); box-shadow: var(--sombra-md); pointer-events: none;
}
.evt-cal__aviso[hidden] { display: none; }
.evt-cal__aviso-linea { display: flex; align-items: center; gap: 7px; white-space: nowrap; }

/* ── El selector radial de la hora ───────────────────────────────────
   Primero la hora, después los minutos, al final a.m. o p.m. Las cifras
   son campos de verdad: se pueden escribir para una hora más precisa. */
.evt-reloj__lectura { display: flex; align-items: center; gap: 8px; margin-bottom: 14px; }
.evt-reloj__lectura .evt-reloj__cifra {
  width: 76px; height: 64px; padding: 0; text-align: center;
  font-size: 34px; font-weight: 400; color: var(--tinta);
  background: var(--gris-100); border: 1.5px solid transparent; border-radius: var(--radio-sm);
}
.evt-reloj__lectura .evt-reloj__cifra.es-activa {
  background: var(--azul-100); border-color: var(--color-azul-institucional);
  color: var(--color-azul-institucional);
}
.evt-reloj__lectura .evt-reloj__cifra:focus-visible { outline: 2px solid var(--azul-600); outline-offset: 2px; }
.evt-reloj__dos-puntos { font-size: 34px; color: var(--gris-700); }
.evt-reloj__meridiano { display: grid; gap: 6px; margin-left: auto; }
.evt-reloj__mer {
  min-width: 56px; padding: 6px 10px; font: inherit; font-size: 13.5px; font-weight: 600;
  color: var(--gris-700); background: var(--color-blanco);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm); cursor: pointer;
}
.evt-reloj__mer:hover { border-color: var(--azul-600); }
.evt-reloj__mer[aria-pressed="true"] {
  background: var(--color-azul-institucional); border-color: var(--color-azul-institucional);
  color: var(--color-blanco);
}

/* La esfera: doce marcas en círculo, la aguja y el centro. Cada marca se
   coloca con su ángulo (`--a`) y se endereza después, para que el
   número no gire con ella. */
.evt-reloj__esfera {
  --radio-esfera: 92px;
  position: relative; width: 236px; height: 236px; margin: 0 auto;
  border-radius: 50%; background: var(--gris-100); touch-action: none; cursor: pointer;
}
.evt-reloj__esfera:focus { outline: none; }
.evt-reloj__marca {
  position: absolute; left: 50%; top: 50%; width: 36px; height: 36px;
  display: grid; place-items: center; border-radius: 50%;
  font-size: 14px; color: var(--tinta); user-select: none;
  transform: translate(-50%, -50%) rotate(var(--a)) translateY(calc(var(--radio-esfera) * -1)) rotate(calc(var(--a) * -1));
}
.evt-reloj__marca.es-elegida { background: var(--color-azul-institucional); color: var(--color-blanco); font-weight: 700; }
.evt-reloj__aguja {
  position: absolute; left: 50%; bottom: 50%; width: 2px; height: var(--radio-esfera);
  background: var(--color-azul-institucional); transform-origin: bottom center;
  transform: translateX(-50%) rotate(var(--a, 0deg));
}
/* La punta de la aguja: cuando el minuto escrito no es múltiplo de 5 no
   hay marca elegida, y es la punta la que dice dónde está. Solo entonces:
   encima de una marca taparía su número. */
.evt-reloj__aguja.con-punta::after {
  content: ''; position: absolute; top: -4px; left: 50%; width: 8px; height: 8px;
  border-radius: 50%; background: var(--color-azul-institucional); transform: translateX(-50%);
}
.evt-reloj__centro {
  position: absolute; left: 50%; top: 50%; width: 8px; height: 8px; border-radius: 50%;
  background: var(--color-azul-institucional); transform: translate(-50%, -50%);
}

@media (max-width: 920px) {
  .evt-bases__rejilla { grid-template-columns: minmax(0, 1fr); gap: 0; }
}
@media (max-width: 520px) {
  .ventana__cajas { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* .types-row · prototipo/EVT/styles.css · los tipos que admite la
   convocatoria, en pastillas. Se leen de un vistazo y envuelven solas. */
.types-row { display: flex; flex-wrap: wrap; gap: 9px; }
.type-pill {
  background: var(--azul-050); color: var(--color-azul-institucional);
  border: 1px solid var(--azul-100); padding: 7px 13px;
  border-radius: 999px; font-size: 13px; font-weight: 600;
}

/* Las bases, en dos columnas de signo contrario. Vivían en un `<style>` de
   `prototipo/EVT/aplicantes/convocatoria-eventos.html`, así que no estaban
   en ninguna de las dos hojas: qué cubre la feria y qué no es la pregunta
   que trae quien abre esto, y en una lista corrida se pierde. */
.bases-cols { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.bases-col { border: 1px solid var(--gris-200); border-radius: var(--radio-sm); padding: 16px 18px; }
.bases-col.is-si { background: var(--ok-050);  border-color: var(--ok-200); }
.bases-col.is-no { background: var(--err-050); border-color: var(--err-200); }
.bases-col h4 {
  margin: 0 0 12px; font-size: 13px; font-weight: 800; letter-spacing: .4px;
  text-transform: uppercase; display: flex; align-items: center; gap: 8px;
}
.bases-col.is-si h4 { color: var(--ok-600); }
.bases-col.is-no h4 { color: var(--err-600); }
/* Una rejilla por concepto: la marca en una columna de ancho fijo y, a su
   derecha, el concepto sobre su detalle. Como la primera columna mide lo
   mismo en todos, los conceptos arrancan en la misma vertical y las dos
   tarjetas cuadran entre sí sin compartir rejilla.

   Era un `flex`, y ahí `<strong>` y el texto que lo sigue eran **dos
   elementos flexibles distintos**: un concepto de tres palabras se partía
   en tres líneas y su detalle quedaba a un costado, en una segunda
   columna que nadie pidió. Es el mismo fallo que trae el prototipo, donde
   no se notaba porque sus conceptos eran de una palabra.

   `align-items: baseline` en la fila de la marca para que el aspa y la
   primera línea del concepto se apoyen en la misma raya. */
.bases-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 13px; }
.bases-list li {
  display: grid; grid-template-columns: 16px minmax(0, 1fr);
  column-gap: 9px; row-gap: 2px; align-items: baseline;
  font-size: 13.5px; color: var(--gris-700); line-height: 1.45;
}
.bases-list .mk { font-weight: 800; }
/* El concepto va en mayúsculas: lo pidió la Coordinación en la ronda de QA del
   28-09-2026. Por CSS y no en el texto, para que el HTML siga diciendo «Sala». */
.bases-list strong {
  grid-column: 2; color: var(--color-azul-institucional-enfoque); text-transform: uppercase;
}
.bases-list li > span:last-child { grid-column: 2; }

/* Donde la tarjeta tiene sitio —apilada dentro del diálogo, o en una
   pantalla ancha— el detalle sube a la misma línea que su concepto y la
   lista se lee como una tabla de tres columnas. Es una consulta de
   **contenedor** y no de ventana: lo que decide si cabe es el ancho de la
   tarjeta, que aquí no se deduce del de la pantalla. */
.bases-col { container-type: inline-size; }

@container (min-width: 430px) {
  .bases-list li { grid-template-columns: 16px max-content minmax(0, 1fr); }
  .bases-list strong { grid-column: 2; white-space: nowrap; }
  .bases-list li > span:last-child { grid-column: 3; }
}

.bases-col.is-si .mk { color: var(--ok-600); }
.bases-col.is-no .mk { color: var(--err-600); }

/* Las condiciones, numeradas con un contador y no con `<ol>` a secas: el
   número va en su propia caja azul, fuera del texto, para que una condición
   de tres renglones no quede colgando bajo su cifra. */
.cond-list { margin: 0; padding: 0; list-style: none; counter-reset: cond; display: grid; gap: 11px; }
.cond-list li {
  counter-increment: cond; position: relative; padding-left: 34px;
  font-size: 14px; color: var(--gris-700); line-height: 1.5;
}
.cond-list li::before {
  content: counter(cond); position: absolute; left: 0; top: 0;
  width: 23px; height: 23px; border-radius: 6px; display: grid; place-items: center;
  background: var(--azul-050); color: var(--azul-600); font-size: 12px; font-weight: 800;
}
.bases-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px; flex-wrap: wrap;
  margin-top: 18px; padding-top: 16px; border-top: 1px solid var(--gris-200);
}
.bases-foot small { font-size: 12.5px; color: var(--gris-500); max-width: 44ch; }
.bases-titulo {
  margin: 26px 0 14px; font-size: 14px;
  color: var(--color-azul-institucional-enfoque);
}

/* Los dos textos sueltos de la columna lateral y el catálogo vacío. */
.info-convocatoria__contacto { margin: 0; font-size: 14px; color: var(--gris-700); line-height: 1.7; }
.info-convocatoria__vacio { margin: 0; font-size: 14px; color: var(--gris-500); }
.info-convocatoria__salida { margin: 18px 0 0; font-size: 14px; color: var(--gris-500); text-align: center; }

/* .cta-bar · prototipo/EVT/styles.css · la acción, en una barra azul al
   final del recorrido de lectura. */
.cta-bar {
  margin-top: 26px; padding: 22px 26px; border-radius: var(--radio);
  background: var(--color-azul-institucional-enfoque); color: var(--color-blanco);
  display: flex; align-items: center; gap: 20px; flex-wrap: wrap;
}
.cta-bar .txt { flex: 1; min-width: 15rem; }
.cta-bar .txt strong { display: block; font-size: 17px; }
.cta-bar .txt span { color: rgba(255, 255, 255, .78); font-size: 13.5px; }

/* .btn-special · prototipo/common/styles-base.css · el botón que va
   rodeado de fondo azul, como el de `.cta-bar`. Un `.btn-primary` ahí
   sería azul sobre azul. */
.btn-special { background: var(--color-blanco); color: var(--color-azul-institucional); box-shadow: var(--sombra-sm); }
.btn-special:hover, .btn-special:focus-visible { background: var(--color-dorado-encabezado); color: var(--color-blanco); text-decoration: none; }

@media (max-width: 720px) {
  .bases-cols { grid-template-columns: 1fr; }
}


/* .grid-3 · prototipo/common/styles-base.css línea 264 */
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0 18px; }

/* .opciones · NUEVO, sin contraparte en el prototipo.
   Una lista larga de casillas —las 59 temáticas de la Ficha de Registro—
   en columnas que fluyen. Con `columns` y no con grid porque el orden que
   importa es el alfabético de arriba abajo: es como está impresa la ficha
   y como la va a recorrer quien la tenga delante. Además reflota sola al
   estrecharse, sin un breakpoint por cada anchura. */
.opciones { columns: 3 190px; column-gap: 22px; }
.opciones label {
  display: flex; align-items: baseline; gap: 8px;
  break-inside: avoid; margin-bottom: 7px;
  font-weight: 500; font-size: 14px; color: var(--gris-700);
}
.opciones input { flex: 0 0 auto; }

/* .casilla-suelta · una casilla sola con su frase, fuera de una lista.
   El rótulo de `.field > label` es negrita y en bloque: para una casilla
   la frase es la voz de quien la marca y va en su peso normal. */
.field > label.casilla-suelta {
  display: flex; align-items: baseline; gap: 8px;
  font-weight: 500; font-size: 14px; color: var(--gris-700); cursor: pointer;
}
.casilla-suelta input { flex: 0 0 auto; }
/* Un botón dentro de una celda, sin romper la fila en dos bloques. */
form.en-linea { display: inline; margin-left: 6px; }

/* El bloque de la factura en el detalle de un abono (A5). */
.factura-solicitada {
  margin: 16px 0; padding: 14px 16px; border: 1px solid var(--gris-200);
  border-radius: var(--radio-sm); background: var(--gris-050);
}
.factura-solicitada > .info-grid { margin: 10px 0 0; }
.factura-solicitada dd { margin: 2px 0 0; }

/* .opcion-larga · una opción cuya descripción no cabe en su etiqueta
   (el tipo de stand y lo que incluye cada uno). */
.opcion-larga { display: flex; flex-direction: column; gap: 10px; }
.opcion-larga label {
  display: flex; gap: 10px; align-items: flex-start;
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  padding: 12px 14px; cursor: pointer; font-size: 14px; color: var(--gris-700);
}
.opcion-larga label:hover { border-color: var(--azul-600); background: var(--azul-050); }
.opcion-larga input { margin-top: 3px; flex: 0 0 auto; }

/* ── Subir un archivo ──────────────────────────────────────────
   El prototipo resuelve esto con `.file-mock`, que es un `<div>`: le
   vale porque nada se sube de verdad. Aquí hace falta un
   `<input type="file">` real, así que se estiliza el nativo en vez de
   taparlo con un control propio. Se gana lo que un control propio
   pierde: teclado, lector de pantalla y el diálogo del sistema, gratis
   y sin una línea de código propio. */
.field input[type="file"] {
  display: block; width: 100%;
  border: 1.5px dashed var(--gris-300); border-radius: var(--radio-sm);
  padding: 11px 13px; background: var(--gris-050);
  font-size: 13.5px; color: var(--gris-500); cursor: pointer;
}
.field input[type="file"]:hover { border-color: var(--azul-600); background: var(--azul-050); }
.field input[type="file"]:focus-visible { outline: 2px solid var(--azul-600); outline-offset: 2px; }

/* El botón del propio input. Se parece a `.btn-ghost` sin serlo: no se
   le puede poner una clase, solo alcanzarlo por este pseudo-elemento. */
.field input[type="file"]::file-selector-button {
  margin-right: 12px; padding: 7px 14px;
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  background: var(--color-blanco); color: var(--color-azul-institucional);
  font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
}
.field input[type="file"]::file-selector-button:hover {
  border-color: var(--azul-600); background: var(--azul-050);
}

/* ── …y en rojo cuando se envió sin él ─────────────────────────
   `.field input.is-invalid` existe desde siempre y **sobre un campo de
   archivo no hacía nada**: mide lo mismo que `.field input[type="file"]`
   —una clase y un atributo cada uno— y la de arriba va después, así que
   ganaba ella y devolvía el borde gris punteado. El resultado era que
   intentar enviar la ficha sin la constancia fiscal dejaba los dos
   adjuntos exactamente igual que antes de pulsar: el único aviso era el
   salto de foco, y la caja a la que saltaba no se distinguía de una
   llena.

   Por eso esta regla y no un retoque de la de arriba: hay que nombrar
   `[type="file"]` **y** `.is-invalid` para pasarle por encima, y así
   queda escrito de quién se está defendiendo.

   El rojo es más cargado que el de una caja de texto inválida —relleno
   `--err-050` entero, no un halo— porque la caja de archivo es grande y
   ya viene con fondo propio: el halo de tres píxeles se perdía contra
   `--gris-050`. El anillo pasa a `--err-200`, que sí se lee encima del
   relleno.

   Y el borde se vuelve **sólido**. Es la mitad que no es color: la
   distancia entre un adjunto pendiente y uno que falta no puede quedar
   en un cambio de tinte, ni en una impresión en blanco y negro ni para
   quien no distingue el rojo (`filey-identidad` §4).

   El texto se queda en `--gris-500`: sobre `--err-050` cumple AA de
   sobra, y ponerlo en `--err-600` lo dejaría en 4.48:1, que es la deuda
   de contraste abierta del proyecto. Se decide entera, no de pasada. */
.field input[type="file"].is-invalid {
  border-style: solid;
  border-color: var(--err-600);
  background: var(--err-050);
  box-shadow: 0 0 0 3px var(--err-200);
}
.field input[type="file"].is-invalid:hover {
  border-color: var(--err-600); background: var(--err-050);
}
.field input[type="file"].is-invalid:focus-visible { outline-color: var(--err-600); }
/* El botón del control, que es lo que de verdad se pulsa: sin esto se
   queda azul de «adjuntar» en medio de una caja que pide atención. */
.field input[type="file"].is-invalid::file-selector-button {
  border-color: var(--err-600);
  color: var(--err-600);
}
.field input[type="file"].is-invalid::file-selector-button:hover {
  border-color: var(--err-600); background: var(--err-050);
}

/* ── …salvo en EVT, donde el rótulo tiene que estar en español ──
   Lo de arriba deja el botón del control nativo, y su texto —«Examinar…»,
   «Choose File»— lo pone el navegador en **su** idioma, no en el de la
   página: no hay CSS que lo cambie. En una convocatoria que se llena en
   español eso desentona, y además el control nativo no dice si el
   archivo entró.

   Así que aquí el input se esconde **dentro de su rótulo** —no se quita
   de la página—: sigue recibiendo el foco, el teclado lo alcanza y el
   lector de pantalla lo anuncia, porque el `<label>` lo envuelve. Lo que
   se ve es el mismo bloque del prototipo, y al elegir un archivo se
   queda en verde con su nombre. Permanente: es la única confirmación
   que hay de que el adjunto se cargó, hasta que se envía. */
.file-mock {
  display: flex; align-items: center; gap: 12px;
  border: 1.5px dashed var(--gris-300); border-radius: var(--radio-sm);
  padding: 12px 14px; background: var(--gris-050); cursor: pointer;
  position: relative;
}
.file-mock:hover { border-color: var(--azul-600); background: var(--azul-050); }
.file-mock:focus-within { border-color: var(--azul-600); background: var(--azul-050); }
.file-mock .ico {
  /* El eje del icono de dentro. La caja mide 34 y el dibujo 18: el hueco
     es lo que lo hace verse como un icono en una pastilla y no como una
     pastilla con un dibujo apretado. */
  --file-mock-ico: 18px;
  width: 34px; height: 34px; border-radius: 8px; flex: 0 0 34px;
  background: var(--azul-100); color: var(--azul-600);
  display: grid; place-items: center;
}
/* `viewBox="0 0 512 512"`, cuadrado. El `calc()` se escribe aunque dé 1:
   documenta el ratio (`filey-ui-componentes` §8). */
.file-mock .ico .ico-svg {
  display: block;
  height: var(--file-mock-ico);
  width: calc(var(--file-mock-ico) * 512 / 512);
}
.file-mock .txt strong { display: block; font-size: 13.5px; color: var(--gris-700); word-break: break-all; }
.file-mock .txt small { color: var(--gris-500); font-size: 12px; }
/* Gana al `.field input[type="file"]` de arriba por especificidad, no por
   orden: si algún día se reordena el archivo, esto sigue en pie. */
.field .file-mock input[type="file"] {
  position: absolute; width: 1px; height: 1px; padding: 0; border: none;
  opacity: 0; overflow: hidden; clip-path: inset(50%);
}
.file-mock.is-cargado {
  border-style: solid; border-color: var(--ok-600); background: var(--ok-050);
}
.file-mock.is-cargado .ico { background: var(--ok-600); color: var(--color-blanco); }

/* Descartar un adjunto ya subido (`CU-EVT-002`). Discreto y debajo, no
   un botón: quitar no es una de las acciones de la pantalla, es el
   arrepentimiento de una. Mismo trato que `.evt-persona__quitar`, y por
   la misma razón — con el mismo truco de Fitts para que el área que
   responde al clic pase de los 44 px sin agrandar el texto. */
/* El componente de subir y el de descartar, en la misma fila: son la
   misma decisión —qué archivo va aquí— y en dos renglones se leían como
   dos cosas distintas.

   `align-items: stretch` para que el botón mida lo que mida la caja sin
   tener que fijarle una altura: la caja crece con su ayuda de dos
   renglones, y una altura escrita a mano se quedaría corta el día que
   cambie ese texto. */
.adjunto-fila {
  /* Sin `gap`: la separación la pone el `margin-left` del botón de
     descartar, que **se anima con él**. Un `gap` no se puede interpolar
     por elemento, así que con él el botón plegado seguiría dejando diez
     píxeles de aire por los que no pasa nada. */
  display: flex; align-items: stretch;
  /* La barra superior es fija, así que un ancla deja su destino
     **debajo** de ella y no se ve (`filey-ux` §5). Hace falta al
     descartar un adjunto por envío normal: el `formaction` con fragmento
     aterriza justo aquí. */
  scroll-margin-top: calc(var(--alto-topbar) + 16px);
}
.adjunto-fila .file-mock { flex: 1; min-width: 0; }

/* Descartar un adjunto (`CU-EVT-002`). El botón entero son `.btn
   .btn-danger .btn-row`, que ya existían: aquí solo va el tamaño del
   icono y el ancho. Repetir los colores habría metido dos hex sueltos
   (E3) y una segunda definición del rojo de peligro.

   Sin rótulo: al lado está el nombre del archivo, y «Descartar el
   archivo» repetía en palabras lo que la papelera dice en un icono. El
   nombre accesible va en `aria-label`, en la plantilla — un botón sin
   texto sin él se anuncia como «botón» y nada más.

   El ancho fijo es la ley de Fitts: 17 px de icono son un objetivo
   minúsculo, y sin esto el botón mediría lo que mide su relleno. */
.evt-descartar {
  --evt-descartar-ico: 17px;
  --evt-descartar-ancho: 46px;
  --evt-descartar-aire: 10px;
  flex: 0 0 var(--evt-descartar-ancho); padding: 0; justify-content: center;
  margin-left: var(--evt-descartar-aire);
  /* El icono no se deforma mientras el botón se estrecha: se recorta. */
  overflow: hidden;
  transition: flex-basis .18s ease, margin-left .18s ease, opacity .14s ease;
}
/* Plegado. **Ocupa cero de verdad** —ni ancho, ni aire— para que el
   botón de adjuntar llegue al borde exactamente igual que cuando no
   había nada que descartar: las dos medidas finales no cambian, lo que
   cambia es que ahora se llega a ellas deslizando.

   `visibility: hidden` además de `opacity` lo saca del recorrido del
   tabulador; sin eso quedaría un objetivo invisible de ancho cero. */
.evt-descartar.esta-plegado {
  flex-basis: 0; margin-left: 0; opacity: 0;
  visibility: hidden; pointer-events: none;
}
/* Quien pidió no animar ve el cambio de golpe, que es lo que había
   antes de esto y funciona igual de bien. */
@media (prefers-reduced-motion: reduce) {
  .evt-descartar { transition: none; }
}
/* `viewBox="0 0 24 24"`, cuadrado. */
.evt-descartar .ico-svg {
  display: block;
  height: var(--evt-descartar-ico);
  width: calc(var(--evt-descartar-ico) * 24 / 24);
}
.file-mock.is-cargado .txt strong { color: var(--ok-600); }

/* ── Un aviso que se puede marcar ──────────────────────────────
   La aceptación de las bases. El aviso entero es la etiqueta de la
   casilla: se marca pulsando en cualquier parte, no solo en el cuadrito
   de 13 píxeles, que en un móvil es un objetivo ridículo.

   Y tiene que **parecerlo**: un `.note` normal es informativo, así que
   sin cursor, sin hover y sin foco visible nadie sabría que se puede
   pulsar. */
.nota-elegible { display: block; padding: 0; }
.nota-elegible > label {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 14px 16px; cursor: pointer;
}
.nota-elegible > label input { margin-top: 2px; }
/* La frase de la casilla en negritas y oscura. Heredaba el dorado de
   `.note-gold` a 13.5px: `--warn-600` sobre `--warn-050` da 3.2:1, bajo AA,
   y es justo la frase que hay que leer antes de enviar. El fondo dorado se
   queda —es lo que la destaca—; lo que cambia es la tinta. En rojo, con
   error, conserva el color del error. */
.nota-elegible.note-gold > label { color: var(--tinta); font-weight: 600; font-size: 14px; }
.nota-elegible > .hint { display: block; padding: 0 16px 14px 50px; }

/* El aviso completo reacciona, no solo la casilla. */
.nota-elegible:hover { filter: brightness(0.98); }
/* El foco del teclado se ve en el aviso entero: el recuadro nativo de la
   casilla se pierde dentro de un bloque de color. */
.nota-elegible:focus-within { outline: 2px solid var(--azul-600); outline-offset: 2px; }
/* Y al marcarla se nota que quedó marcada, que es lo que confirma que el
   paso obligatorio está resuelto. */
.nota-elegible:has(input:checked) { border-color: var(--ok-600); }

/* ═══ El mapa del showfloor (STD · CU-STD-009, 032) ═══════════
   Lo dibuja un canvas de Godot en un `<iframe>` (ADR-0008), así que aquí
   no hay nada del plano: solo su marco, el velo que lo tapa mientras
   carga, y la tarjeta de detalle — que según el contrato del componente
   es responsabilidad de esta página, no del canvas. */

.mapa-cabecera {
  display: flex; flex-wrap: wrap; gap: 14px 26px;
  align-items: flex-end; justify-content: space-between; margin-bottom: 14px;
}
.mapa-cabecera h3 { margin: 0 0 2px; font-size: 18px; color: var(--color-azul-institucional); }
.mapa-cabecera p { margin: 0; }

.mapa-leyenda { display: flex; flex-wrap: wrap; gap: 8px 18px; list-style: none; margin: 0; padding: 0; }
.mapa-leyenda li { display: flex; align-items: center; gap: 7px; font-size: 13px; color: var(--gris-700); }
.mapa-muestra { width: 15px; height: 11px; border-radius: 2px; flex: 0 0 auto; }
/* Los mismos tres colores que usa el canvas para los mismos tres
   estados. Si divergen, la leyenda miente. */
.mapa-muestra.es-disponible { background: var(--ok-600); }
.mapa-muestra.es-reservado { background: var(--color-dorado-encabezado); }
.mapa-muestra.es-ocupado { background: var(--err-600); }
/* El pizarra del canvas (`event-stand-map`, #3f4a5a): un espacio que la
   feria retiene no es un veredicto, así que va en neutro (CU-STD-043). */
.mapa-muestra.es-bloqueado { background: var(--gris-700); }

/* El marco es el sistema de referencia de la tarjeta: `screenRect` llega
   en píxeles relativos a la esquina del `<iframe>`, así que el
   contenedor tiene que ser el bloque que posiciona. */
.mapa-marco {
  position: relative; background: var(--color-blanco);
  border: 1px solid var(--gris-200); border-radius: var(--radio);
  box-shadow: var(--sombra-sm); overflow: hidden;
  /* El alto lo pone la rejilla, para que el mapa y el carrito midan lo
     mismo. Fijo y no `aspect-ratio`: el recinto es muy apaisado
     (167 × 59) y su proporción dejaría el canvas de dos dedos de alto en
     un portátil. Dentro se recorre con zoom, que es para lo que está. */
  height: 100%;
}
.mapa-marco iframe { display: block; width: 100%; height: 100%; border: 0; }

/* El velo tapa el arranque de Godot con el chasis de FILEY. Lo quita el
   puente al recibir `ready`; si los datos fallan, se queda con su otro
   mensaje — un canvas vacío sin explicación se lee como "no hay
   espacios libres", que es lo contrario de lo que pasa. */
.mapa-velo {
  position: absolute; inset: 0; z-index: 2;
  display: flex; align-items: center; justify-content: center;
  background: var(--gris-050); text-align: center;
}
/* **Tiene que ir después del `display: flex` de arriba.** El atributo
   `hidden` esconde con el `display: none` de la hoja del navegador, y
   cualquier `display` de una hoja propia le gana: sin esta regla,
   `velo.hidden = true` no hace nada y el mapa se queda dibujado detrás
   de la pantalla de carga, para siempre. */
.mapa-velo[hidden] { display: none; }

.mapa-velo__mensaje { display: none; flex-direction: column; gap: 4px; }
.mapa-velo:not([data-estado]) [data-cargando] { display: flex; }
.mapa-velo[data-estado="error"] [data-error] { display: flex; }

/* El detalle va **centrado en la pantalla**, no anclado al espacio.
   Anclado quedaba dentro de `.mapa-marco`, que recorta lo que se sale
   (`overflow: hidden`), así que un espacio de la orilla abría su tarjeta
   medio cortada; y en el resto de los casos tapaba justo los espacios de
   al lado, que son los que uno está comparando.

   `fixed` y no `absolute`: centrado respecto a la ventana, y fuera del
   recorte del marco. */
.mapa-tarjeta-fondo {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(16, 26, 48, .45);
  /* Lo que hay detrás es un plano denso y a todo color. Un velo plano lo
     atenúa pero no lo calla: la retícula sigue compitiendo con el texto
     del diálogo. El desenfoque es lo que hace que el plano se lea como
     fondo. Donde no esté soportado queda el velo de siempre. */
  backdrop-filter: blur(3px);
}
.mapa-tarjeta-fondo[hidden] { display: none; }
.mapa-tarjeta {
  position: fixed; z-index: 41;
  top: 50%; left: 50%; transform: translate(-50%, -50%);
  /* Más ancho que la tarjeta de antes: ahora lleva el detalle entero
     —superficie, medidas, precio, zona y qué incluye— y no un resumen. */
  width: 460px; max-width: calc(100vw - 32px);
  /* Mismo par de razones que en `.modal`. */
  max-height: calc(100dvh - 48px); overflow: auto;
  background: var(--color-blanco); border: 1px solid var(--gris-300);
  border-radius: var(--radio); box-shadow: var(--sombra-lg); padding: 20px 22px;
}
/* Igual que el velo: el `display` de arriba gana sobre el del
   atributo `hidden`, así que hace falta decirlo. */
.mapa-tarjeta[hidden] { display: none; }

/* El diálogo entra en un solo movimiento: el velo aparece y la tarjeta
   sube diez píxeles. Sobre un plano que no se mueve, algo que se
   materializa de golpe no se lee como «esto es lo que acabas de pulsar».
   `hidden` no se puede transicionar —va de `display:none` a visible—,
   así que es una animación de entrada y no una transición. */
@keyframes mapa-tarjeta-entra {
  from { opacity: 0; transform: translate(-50%, calc(-50% + 10px)); }
  to   { opacity: 1; transform: translate(-50%, -50%); }
}
@keyframes mapa-tarjeta-fondo-entra { from { opacity: 0; } to { opacity: 1; } }
.mapa-tarjeta:not([hidden]) {
  animation: mapa-tarjeta-entra 170ms cubic-bezier(.2, .8, .2, 1) both;
}
.mapa-tarjeta-fondo:not([hidden]) {
  animation: mapa-tarjeta-fondo-entra 170ms ease-out both;
}
@media (prefers-reduced-motion: reduce) {
  .mapa-tarjeta:not([hidden]),
  .mapa-tarjeta-fondo:not([hidden]) { animation: none; }
}

/* ── El encabezado del diálogo ──────────────────────────────────────
   Dos renglones en vez de «Espacio A-14» a 16 px. La clave es el dato
   —es lo que está escrito en el plano y lo que se dice en voz alta—, y
   la palabra «Espacio» es su etiqueta; separarlas deja que la clave
   tenga el tamaño que le toca sin arrastrar el resto de la frase. */
.mapa-tarjeta__encabezado { margin-bottom: 16px; padding-right: 40px; }
.mapa-tarjeta__cepillo {
  margin: 0; font-size: 12px; font-weight: 700; line-height: 1;
  text-transform: uppercase; letter-spacing: .5px; color: var(--gris-500);
}
.mapa-tarjeta h4 {
  margin: 5px 0 0; font-size: 26px; font-weight: 800; line-height: 1.1;
  color: var(--color-azul-institucional);
}
.mapa-tarjeta__cerrar,
.modal-cerrar {
  position: absolute; top: 12px; right: 12px;
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; padding: 0;
  background: none; border: none; cursor: pointer;
  font-size: 15px; line-height: 1; color: var(--gris-500);
  border-radius: var(--radio-sm);
}
/* El área pulsable es mayor que el dibujo (`filey-ui-componentes` §7):
   una ✕ se quiere pequeña y se necesita grande, y agrandar el botón a
   ojo lo convertiría en un elemento con peso. */
.mapa-tarjeta__cerrar::after,
.modal-cerrar::after { content: ""; position: absolute; inset: -8px; }
.mapa-tarjeta__cerrar:hover,
.modal-cerrar:hover { background: var(--gris-100); color: var(--gris-700); }
.mapa-tarjeta__cerrar:focus-visible,
.modal-cerrar:focus-visible {
  outline: 2px solid var(--color-azul-institucional); outline-offset: 2px;
}

/* ── El detalle de un espacio ────────────────────────────────────────
   Se pinta en dos sitios —el diálogo del mapa y la pantalla propia—, así
   que no depende de ninguno de los dos: solo pone su propio ritmo de
   filetes horizontales. */
.espacio-cinta {
  display: flex; align-items: center; flex-wrap: wrap; gap: 10px;
  margin: 0 0 16px;
}

/* El precio y la planta, en el mismo renglón y leyéndose uno contra el
   otro. Es la pregunta con la que se abre esto. */
.espacio-cifras {
  display: flex; align-items: center; gap: 20px;
  padding-top: 16px; border-top: 1px solid var(--gris-200);
}
/* Caja fija y `preserveAspectRatio` dentro: la proporción es un dato de
   cada espacio, así que no se puede derivar del `viewBox` con un
   `calc()` como en los iconos (`filey-ui-componentes` §8 regla 3). Un
   6×1 queda como una banda y un 3×2 casi cuadrado, que es justo lo que
   este dibujo existe para enseñar. */
.espacio-huella {
  flex: 0 0 auto; width: 86px; height: 64px;
  color: var(--color-azul-institucional);
}
.espacio-huella rect { fill: var(--gris-100); stroke: currentColor; stroke-width: 1.5; }
.espacio-precio { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.espacio-precio__cifra {
  font-size: 28px; font-weight: 800; line-height: 1.05;
  color: var(--color-azul-institucional);
}
.espacio-precio__medidas { font-size: 14px; font-weight: 600; color: var(--gris-700); }
.espacio-precio__tarifa { font-size: 13px; color: var(--gris-500); }

.espacio-bloque {
  margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--gris-200);
}
.espacio-bloque__titulo {
  margin: 0 0 10px; font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .5px; color: var(--gris-500);
}
/* El rótulo queda fuera de este reajuste a propósito: `.espacio-bloque p`
   es clase + elemento y le gana a `.espacio-bloque__titulo`, que es solo
   clase, así que se llevaba por delante su margen inferior y el título
   quedaba pegado al contenido. */
.espacio-bloque > p:not(.espacio-bloque__titulo) { margin: 0; }
/* Bloquear o desbloquear (CU-STD-043): la consecuencia encima del
   botón, que va dentro del formulario y por eso no le alcanza la regla de
   arriba. */
.espacio-disponibilidad form p { margin: 0 0 12px; font-size: 14px; color: var(--gris-700); }
/* Dentro de un bloque el filete ya lo puso el bloque: el de
   `.form-actions` sería el segundo a diez píxeles del primero. */
.espacio-bloque .form-actions {
  margin-top: 14px; padding-top: 0; border-top: 0; justify-content: flex-start;
}

/* En el diálogo, «Agregar a mi selección» es lo único que se puede hacer
   desde aquí: ocupa el ancho en vez de dejar medio renglón vacío al
   lado. En la pantalla propia no, que ahí acompaña a otros dos enlaces. */
.mapa-tarjeta__cuerpo .espacio-acciones form { flex: 1; }
.mapa-tarjeta__cuerpo .espacio-acciones .btn { width: 100%; }

.badge.es-disponible { background: var(--ok-050); color: var(--ok-600); }
.badge.es-reservado { background: var(--warn-050); color: var(--color-dorado-800); }
.badge.es-ocupado { background: var(--err-050); color: var(--err-600); }
.badge.es-bloqueado { background: var(--gris-100); color: var(--gris-700); }

/* ═══ El desglose del precio (STD · CU-STD-012, 013, 029) ══════
   Una tabla de tres columnas —concepto, importe, subtotal— donde lo que
   importa es poder seguir la cuenta con el dedo. Por eso los números van
   con `tabular-nums`: sin cifras de ancho fijo, las columnas bailan y
   comparar dos renglones deja de ser inmediato. */

.tabla-desglose td.num, .table td.num, .table th.num { text-align: right; }
.tabla-desglose td { font-variant-numeric: tabular-nums; }
.tabla-desglose th { font-weight: 600; text-align: left; }
.tabla-desglose th .hint { margin-left: 6px; }
.tabla-desglose tr.es-total { border-top: 2px solid var(--gris-300); }
.tabla-desglose tr.es-total td, .tabla-desglose tr.es-total th {
  font-weight: 700; color: var(--color-azul-institucional);
}
/* Un espacio que se perdió mientras la persona decidía. Apagado, no
   tachado: sigue siendo legible porque hay que poder leer cuál era. */
tr.es-no-disponible { opacity: .55; }

/* =========================================================
   Chasis del panel de administración (sidebar + contenido)
   Portado del prototipo de EVT —mismos nombres de clase y de token—,
   traído al usarse por primera vez en Django.

   Se sigue el sidebar **persistente** de EVT y no el overlay de VIS.
   El de VIS lo inyecta `app.js` y abre al pasar el ratón: si ese script
   no llega, no existe, y entonces no habría ninguna forma de navegar el
   panel. Navegar es una función, y ninguna puede depender del navegador
   (`ADR-0008`).
   ========================================================= */

/* La altura de la barra superior, nombrada una vez. El prototipo la
   escribe como 67px en cuatro sitios; aquí el día que la barra cambie de
   alto se toca esto y nada más. */
:root { --alto-topbar: 67px; }

/* El `min-height` se queda: la barra lateral es `sticky` y mide
   `100vh - topbar`, así que el chasis de administración ya llena la
   ventana por su cuenta y el pie cae debajo del pliegue **a propósito**.
   Empujarlo al borde inferior exigiría restarle al alto de la barra un
   pie que se dobla en pantalla estrecha. */
.admin-body { display: flex; min-height: calc(100vh - var(--alto-topbar)); }

.sidebar {
  width: 240px; flex: 0 0 240px;
  background: var(--color-blanco);
  border-right: 1px solid var(--gris-200);
  padding: 14px 12px;
  /* Pegada bajo la barra superior, que también es `sticky`: el menú se
     queda a la vista en un listado de doscientas reservas. */
  position: sticky; top: var(--alto-topbar); align-self: flex-start;
  height: calc(100vh - var(--alto-topbar));
  overflow-y: auto;
  display: flex; flex-direction: column;
}
.side-section {
  font-size: 11px; text-transform: uppercase; letter-spacing: 1.2px;
  color: var(--gris-500); font-weight: 700; padding: 14px 12px 6px;
}
.side-nav { display: flex; flex-direction: column; gap: 2px; }
.side-link {
  display: flex; align-items: center; gap: 13px;
  padding: 11px 13px; border-radius: var(--radio-sm);
  color: var(--gris-700); font-size: 14.5px; font-weight: 600;
  border-left: 3px solid transparent; cursor: pointer;
}
.side-link:hover { background: var(--gris-050); text-decoration: none; color: var(--color-azul-institucional); }
.side-link .ico { font-size: 18px; width: 22px; text-align: center; }
.side-link.active {
  background: var(--azul-100); color: var(--color-azul-institucional-enfoque);
  border-left-color: var(--color-azul-institucional);
}
.side-link:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: -2px; }
.side-link.is-disabled { color: var(--gris-300); cursor: not-allowed; }
.side-link.is-disabled:hover { background: transparent; color: var(--gris-300); }

/* La salida, al pie y despegada del resto: no es una sección más. */
.side-foot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--gris-200); }

.admin-main { flex: 1; min-width: 0; background: var(--gris-050); }
.admin-toolbar {
  background: var(--color-blanco); border-bottom: 1px solid var(--gris-200);
  min-height: 48px; display: flex; align-items: center; gap: 12px; padding: 0 16px;
}
.admin-toolbar .crumb { font-size: 13px; color: var(--gris-500); }
.admin-content { padding: 26px 30px 60px; max-width: 1280px; }

/* En pantalla chica la barra se tumba en una tira horizontal encima del
   contenido, y **no** se convierte en un cajón: un cajón necesita algo
   que lo abra, y si eso no corre no habría navegación. Una tira que se
   desplaza de lado es peor de ver y sigue funcionando. */
@media (max-width: 920px) {
  .admin-body { display: block; }
  .sidebar {
    width: auto; flex: none; height: auto; position: static;
    border-right: none; border-bottom: 1px solid var(--gris-200);
    display: block; overflow-x: auto;
  }
  .sidebar nav { display: flex; gap: 18px; align-items: center; }
  .side-section { display: none; }
  .side-nav { flex-direction: row; gap: 4px; }
  .side-link { white-space: nowrap; border-left: none; border-bottom: 3px solid transparent; }
  .side-link.active { border-left: none; border-bottom-color: var(--color-azul-institucional); }
  .side-foot { margin: 0; padding: 0 0 8px; border-top: none; }
  .admin-content { padding: 20px 16px 48px; }
}

/* El botón que alterna la barra, como el `sidenav.toggle()` del prototipo
   de STD. Sin Alpine no se pinta —`[x-cloak]` está declarado una sola vez,
   arriba— y la barra se queda abierta, que es su estado por omisión
   —`mode="side" opened`— y el útil. */
.admin-toolbar .menu-btn {
  background: none; border: none; cursor: pointer; font-size: 20px;
  color: var(--gris-700); padding: 4px 8px; border-radius: var(--radio-sm);
  line-height: 1;
}
.admin-toolbar .menu-btn:hover { background: var(--gris-100); }
.admin-toolbar .menu-btn:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px; }
.sidebar.is-cerrada { display: none; }

/* ═══ El panel de un módulo (STD) ══════════════════════════════
   `.stat-grid` y `.stat-card` vienen del prototipo de EVT —mismos
   nombres de clase y de token—, traídas al usarse por primera vez en
   Django. Lo que se añade aquí es que la tarjeta sea **pulsable**: en el
   prototipo son cifras y aquí cada una es la puerta a donde se resuelve
   ese pendiente. */

.panel-titulo {
  font-size: 13px; text-transform: uppercase; letter-spacing: .1em;
  color: var(--gris-500); font-weight: 700; margin: 26px 0 12px;
}
.panel-titulo:first-child { margin-top: 0; }
.panel-ajustes { display: block; margin-top: 22px; }

.stat-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px; margin-bottom: 24px;
}
.stat-card {
  background: var(--color-blanco); border: 1px solid var(--gris-200);
  border-radius: var(--radio); box-shadow: var(--sombra-sm);
  padding: 16px 18px; display: flex; flex-direction: column; gap: 4px;
}
.stat-card .k { font-size: 12.5px; color: var(--gris-500); text-transform: uppercase; letter-spacing: .6px; font-weight: 700; }
.stat-card .v { font-size: 30px; font-weight: 800; color: var(--color-azul-institucional-enfoque); line-height: 1; font-variant-numeric: tabular-nums; }
.stat-card .foot { font-size: 12.5px; color: var(--gris-500); }
.stat-card.accent-blue { border-top: 3px solid var(--color-azul-institucional); }
.stat-card.accent-gold { border-top: 3px solid var(--color-dorado-encabezado); }
.stat-card.accent-ok { border-top: 3px solid var(--ok-600); }
.stat-card.accent-warn { border-top: 3px solid var(--warn-600); }
.stat-card.accent-err { border-top: 3px solid var(--err-600); }

/* Pulsable: sin esto parece una cifra y no una puerta. */
a.stat-card { text-decoration: none; }
a.stat-card:hover { border-color: var(--gris-300); box-shadow: var(--sombra-md); }
a.stat-card:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px; }

/* La ocupación del recinto, en metros y no en espacios: vender treinta
   espacios chicos no es vender tres grandes, y lo que sigue el dinero es
   la superficie. Una sola barra con los tres tramos, porque lo que se lee
   de un vistazo es la proporción entre ellos. */
.ocupacion-cabecera {
  display: flex; flex-wrap: wrap; gap: 12px 26px;
  align-items: baseline; justify-content: space-between; margin-bottom: 12px;
}
.ocupacion-cifra {
  font-size: 32px; font-weight: 800; line-height: 1;
  color: var(--color-azul-institucional-enfoque);
  font-variant-numeric: tabular-nums; margin-right: 8px;
}
.ocupacion-barra {
  display: flex; height: 14px; border-radius: 999px; overflow: hidden;
  background: var(--gris-100); margin-bottom: 20px;
}
.ocupacion-barra span { display: block; min-width: 2px; }
.ocupacion-barra .es-disponible { background: var(--ok-600); }
.ocupacion-barra .es-reservado { background: var(--color-dorado-encabezado); }
.ocupacion-barra .es-ocupado { background: var(--err-600); }
.ocupacion-barra .es-bloqueado { background: var(--gris-700); }

/* ═══ Mapa y carrito, lado a lado (STD · CU-STD-009 y 011) ═════
   El prototipo de STD pone el mapa a la izquierda y la selección a la
   derecha, y agregar un espacio actualiza la columna sin sacarte del
   plano. 70/30 en vez de su `1fr 380px`: con 151 espacios el mapa
   necesita todo el ancho que se le pueda dar, y el carrito no crece con
   la selección — sus filas son de una línea. */

/* Sin carrito —el mapa de quien administra— el lienzo va a todo el
   ancho. Con la rejilla de dos columnas quedaría un 30 % vacío al lado. */
.mapa-solo { height: min(72vh, 640px); min-height: 460px; margin-bottom: 14px; }

/* Mapa arriba, selección abajo — **en todos los tamaños** desde el
   20-09-2026. Antes eran dos columnas, 70 % para el mapa y el resto para
   el carrito, y solo por debajo de 920 px se apilaban.

   El recinto mide 167 × 59 celdas: es casi tres veces más ancho que
   alto, así que lo que decide cuánto se ve **es el ancho**. Devolverle
   ese 30 % lo agranda un 43 % sin pedirle un píxel a la altura, que es
   justo lo que una columna estrecha al lado no podía dar.

   Y el orden de lectura no cambia: primero se elige en el mapa, después
   se revisa lo elegido. Era ya el orden de la versión apilada. */
.mapa-con-carrito {
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin-bottom: 14px;
}
/* El alto ya no lo comparten, así que cada uno pide el suyo. El del mapa
   se queda por debajo del que tenía: a todo lo ancho, el plano deja de
   estar limitado por la altura mucho antes, y lo que sobra sería franja
   vacía que solo aleja el carrito. */
.mapa-con-carrito .mapa-marco { height: min(62vh, 540px); min-height: 380px; }
/* La lista se desplaza dentro (`.carrito-cuerpo`), así que el tope es lo
   que impide que veinte espacios empujen el resto de la página. */
.mapa-con-carrito .carrito-lateral { max-height: 440px; }

.carrito-lateral {
  display: flex; flex-direction: column; min-height: 0;
  background: var(--color-blanco); border: 1px solid var(--gris-200);
  border-radius: var(--radio); box-shadow: var(--sombra-sm); overflow: hidden;
}
.carrito-cabecera {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px; padding: 14px 16px; border-bottom: 1px solid var(--gris-200);
}
.carrito-cabecera h3 {
  margin: 0; font-size: 16px; display: flex; align-items: center; gap: 8px;
  color: var(--color-azul-institucional);
}
.carrito-cuenta {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 7px; border-radius: 999px;
  background: var(--color-dorado-encabezado); color: var(--color-blanco);
  font-size: 12.5px; font-weight: 700;
}
.carrito-aviso { margin: 12px 16px 0; }

/* La lista se desplaza sola: sin esto, una selección de veinte espacios
   estira la columna y el «Continuar» se va debajo del pliegue. */
.carrito-cuerpo {
  flex: 1; overflow-y: auto; min-height: 0;
  display: flex; flex-direction: column; gap: 8px; padding: 12px 16px;
}
/* Cada espacio en su propia tarjeta. Separados solo por una raya, en la
   rejilla de columnas el precio de uno quedaba pegado a la clave del
   siguiente, y no se sabía qué cifra era de quién. */
.carrito-linea {
  display: grid; grid-template-columns: auto 1fr auto auto;
  align-items: center; gap: 10px; padding: 9px 8px 9px 12px;
  background: var(--gris-050); border: 1px solid var(--gris-200);
  border-radius: var(--radio-sm);
}
.carrito-clave {
  font-weight: 700; color: var(--color-azul-institucional-enfoque);
  font-variant-numeric: tabular-nums;
}
.carrito-datos { font-size: 13px; color: var(--gris-700); min-width: 0; }
.carrito-precio { font-variant-numeric: tabular-nums; font-weight: 600; }
.carrito-quitar {
  background: none; border: none; cursor: pointer; padding: 4px 6px;
  border-radius: var(--radio-sm); color: var(--gris-500); line-height: 1;
}
.carrito-quitar:hover { background: var(--err-050); color: var(--err-600); }
.carrito-quitar:focus-visible { outline: 2px solid var(--err-600); outline-offset: 1px; }
.carrito-linea.es-no-disponible { opacity: .55; border-style: dashed; }

.carrito-pie {
  padding: 14px 16px; border-top: 1px solid var(--gris-200);
  background: var(--gris-050);
}
/* El resumen del pie: el total, con su rótulo, y debajo un renglón con
   lo que lo explica. */
.carrito-resumen { min-width: 0; }
.carrito-total {
  margin: 0; display: flex; align-items: baseline; justify-content: space-between; gap: 12px;
}
.carrito-total__rotulo { font-size: 14px; font-weight: 700; color: var(--gris-700); }
.carrito-total__cifra {
  font-size: 20px; font-weight: 800; font-variant-numeric: tabular-nums;
  color: var(--color-azul-institucional-enfoque);
}
.carrito-detalle { margin: 4px 0 0; font-size: 13px; color: var(--gris-700); }
.carrito-pie .btn { margin-top: 14px; }

/* ── …y el mismo carrito cuando va a todo el ancho ──────────
   Consecuencia de apilarlo bajo el mapa, no un rediseño aparte: la lista
   fluye en columnas de tarjetas y el pie se pone en una sola línea, con el
   resumen a la izquierda y el botón a la derecha. Solo aquí: en el móvil
   el carrito vuelve a ser una columna estrecha, y ahí el pie apilado es el
   que funciona. */
@media (min-width: 721px) {
  /* La lista fluye en columnas en vez de estirar cada fila a todo lo
     ancho. Es la otra mitad de lo mismo: `.carrito-linea` reparte
     `auto 1fr auto auto` y ese `1fr` —los m²— se comía el hueco entre la
     clave y el precio, que a 300 px eran un palmo y a 1 100 px son media
     pantalla.

     Las columnas se llenan solas a partir de 260 px, que es el ancho al
     que la fila fue diseñada. De paso el carrito se acorta: con tres
     columnas, doce espacios ocupan cuatro renglones en vez de doce, y lo
     que se ahorra es scroll entre el mapa y el botón de confirmar. */
  .mapa-con-carrito .carrito-cuerpo {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
    gap: 10px 12px;
    align-content: start;
  }

  /* A todo el ancho: el resumen a la izquierda y el botón a la derecha,
     centrados en la misma línea. El total deja de repartirse en extremos
     opuestos: rótulo y cifra van juntos. */
  .mapa-con-carrito .carrito-pie {
    display: flex; align-items: center; justify-content: space-between; gap: 24px;
  }
  .mapa-con-carrito .carrito-total { justify-content: flex-start; }
  /* `btn-block` lo estiraría a todo el ancho del pie. */
  .mapa-con-carrito .carrito-pie .btn { margin-top: 0; width: auto; flex: 0 0 auto; }
}

/* El vacío invita, no regaña: dice qué hacer para llenarlo. */
.carrito-vacio {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: center; text-align: center; padding: 24px 20px; gap: 2px;
}
.carrito-vacio .ico { font-size: 30px; opacity: .5; }
.carrito-vacio p { margin: 0; }

/* Apilar ya no es cosa de este `@media`: es el comportamiento normal
   desde el 20-09-2026. Lo que queda es dejarle al mapa menos pantalla
   donde la pantalla es toda la que hay. */
@media (max-width: 920px) {
  .mapa-con-carrito { gap: 14px; }
  .mapa-con-carrito .mapa-marco { height: min(58vh, 420px); min-height: 300px; }
  .mapa-con-carrito .carrito-lateral { max-height: 380px; }
}

/* ═══ El flujo del expositor (STD · U1 → U5) ═══════════════════
   Tres componentes que solo existen aquí y por eso no llevan prefijo de
   dominio: en Django no hay más flujo largo que este. El día que EVT
   tenga el suyo, `.pasos` sube a la capa común tal cual — no depende de
   ningún token de STD. */

/* La barra de pasos. Ley Zeigarnik de `filey-identidad`: un trámite que
   dura semanas tiene que decir siempre dónde se está y qué falta.

   `flex-wrap` y no un `@media`: son cinco pasos con nombres cortos, y en
   pantalla chica se parten solos en dos filas sin perder el orden. */
.pasos {
  display: flex; flex-wrap: wrap; gap: 6px 4px;
  list-style: none; margin: 0 0 22px; padding: 0;
}
.paso {
  display: flex; align-items: center; gap: 8px;
  font-size: 13px; color: var(--gris-500);
  padding: 6px 14px 6px 10px;
  border-bottom: 2px solid var(--gris-200);
  flex: 1 1 120px;
}
.paso a { display: flex; align-items: center; gap: 8px; color: inherit; }
.paso a:hover { color: var(--color-azul-institucional); }
.paso-num {
  flex: 0 0 22px; height: 22px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--gris-100); color: var(--gris-500);
  font-size: 12px; font-weight: 700; line-height: 1;
}
.paso-nombre { white-space: nowrap; }
.paso.es-hecho { color: var(--gris-700); border-bottom-color: var(--ok-600); }
.paso.es-hecho .paso-num { background: var(--ok-050); color: var(--ok-600); }
.paso.es-actual {
  color: var(--color-azul-institucional); font-weight: 700;
  border-bottom-color: var(--color-azul-institucional);
}
.paso.es-actual .paso-num {
  background: var(--color-azul-institucional); color: var(--color-blanco);
}

/* Pestañas. Alpine las alterna y sin él se ven las dos secciones: por
   eso el estado activo va en una clase y no en `hidden`. */
.pestanas-tira {
  display: flex; gap: 4px; margin-bottom: 18px;
  border-bottom: 1px solid var(--gris-200);
}
.pestana {
  display: flex; align-items: center; gap: 8px;
  background: none; border: none; cursor: pointer;
  padding: 10px 16px; margin-bottom: -1px;
  font: inherit; font-size: 14px; font-weight: 600; color: var(--gris-500);
  border-bottom: 2px solid transparent;
  text-decoration: none;
}
.pestana:hover { color: var(--color-azul-institucional); text-decoration: none; }
.pestana:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: -2px; }
.pestana.is-active {
  color: var(--color-azul-institucional);
  border-bottom-color: var(--color-azul-institucional);
}
.pestana-cuenta {
  min-width: 20px; padding: 0 6px; border-radius: 10px;
  background: var(--gris-100); color: var(--gris-700);
  font-size: 12px; font-variant-numeric: tabular-nums;
}
/* En un teléfono no caben cuatro pestañas (desde la de facturación,
   `CU-STD-040`): la tira se desplaza de lado en vez de empujar la página.
   Con `overflow` la tira recorta lo que asoma de su caja, y la raya de la
   pestaña activa asoma 1px sobre el borde inferior: por eso ahí el borde
   pasa a sombra interior y la pestaña deja de solaparlo. */
@media (max-width: 920px) {
  .pestanas-tira {
    overflow-x: auto; scrollbar-width: none;
    border-bottom: none; box-shadow: inset 0 -1px 0 var(--gris-200);
  }
  .pestana { flex: 0 0 auto; white-space: nowrap; padding: 10px 12px; margin-bottom: 0; }
}

/* El progreso del pago, con sus dos umbrales dibujados: al 50% la
   reserva se confirma (RN-13) y al 100% queda pagada (RN-14). Una barra
   sin las marcas solo dice "vas por la mitad" y no qué pasa al llegar. */
.progreso { margin-top: 18px; }
.progreso-cabecera { display: flex; justify-content: space-between; align-items: baseline; }
.progreso-cabecera strong { font-variant-numeric: tabular-nums; }
.progreso-barra {
  position: relative; height: 10px; margin: 6px 0 4px;
  background: var(--gris-100); border-radius: 999px; overflow: hidden;
}
.progreso-hecho { display: block; height: 100%; background: var(--ok-600); }
/* La posición la pone la plantilla (`style="left: N%"`), porque el
   umbral que confirma es de la convocatoria y no del sistema (RN-02).
   Aquí no hay `left` de reserva a propósito: un valor por omisión
   dibujaría la marca en un sitio distinto del que confirma. */
.progreso-marca {
  position: absolute; top: 0; bottom: 0;
  width: 2px; background: var(--color-blanco);
  /* El contorno no es adorno: en blanco sobre el tramo sin cubrir la
     marca desaparecía —el track es casi blanco—, justo en el caso en
     que el umbral importa, que es cuando todavía no se alcanzó. */
  box-shadow: 0 0 0 1px rgba(27, 35, 48, .28);
}
.progreso-marcas {
  display: flex; justify-content: space-between;
  font-size: 12px; color: var(--gris-500);
}
.progreso-marcas .es-alcanzada { color: var(--ok-600); font-weight: 600; }

/* ═══ U5 · el estado de cuenta (STD · CU-STD-013, 014) ═════════════
   La pregunta que trae aquí a una editorial es una sola —«¿cuánto debo
   y para cuándo?»— y se contestaba en la cuarta celda de una rejilla de
   cuatro, con el mismo peso que el subtotal y dentro de la tarjeta del
   desglose. Aquí es la primera cifra de la pantalla; el desglose pasa a
   ser su respaldo, que es el papel que le toca.

   La jerarquía la hace **solo la tipografía**: la misma tarjeta blanca
   del resto del sistema, sin fondo de color ni degradado, con el saldo a
   32 px en tinta y su etiqueta a 11.5 px en versalitas. Es un estado de
   cuenta y se lee como uno impreso: cifras alineadas, filetes finos y
   nada que compita con los números. */
.estado-cuenta {
  background: var(--color-blanco);
  border: 1px solid var(--gris-200);
  border-radius: var(--radio);
  box-shadow: var(--sombra-sm);
  margin-bottom: 22px;
  overflow: hidden;
}
.estado-cuenta-cuerpo { padding: 24px 26px 22px; }

.estado-cuenta-cabecera {
  display: flex; flex-wrap: wrap; gap: 18px 32px;
  align-items: flex-start; justify-content: space-between;
}
.estado-cuenta-etiqueta {
  margin: 0; font-size: 11.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: .08em; color: var(--gris-500);
}
/* El salto de la etiqueta a la cifra es toda la jerarquía de la
   pantalla. En tinta y no en azul institucional: el azul es de los
   títulos, y esto no es un título sino el dato. */
.estado-cuenta-monto {
  margin: 4px 0 0; font-size: 32px; line-height: 1.1; font-weight: 700;
  letter-spacing: -.01em; font-variant-numeric: tabular-nums; color: var(--tinta);
}
.estado-cuenta-de { margin: 7px 0 0; font-size: 13.5px; color: var(--gris-500); }
.estado-cuenta-de strong { color: var(--gris-700); font-variant-numeric: tabular-nums; font-weight: 600; }

/* Lo reportado y todavía sin validar. Va junto a la cifra porque explica
   por qué el saldo no bajó después de registrar un pago (`RN-13`): sin
   esta línea, la pantalla parece no haberse enterado. El punto ámbar es
   el color de «pendiente de validación» en todo el sistema. */
.estado-cuenta-revision {
  margin: 10px 0 0; font-size: 13px; color: var(--gris-700);
  display: flex; align-items: baseline; gap: 7px;
}
.estado-cuenta-revision::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--warn-600); flex: 0 0 7px;
}
.estado-cuenta-revision strong { font-variant-numeric: tabular-nums; }

.estado-cuenta-accion {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 10px; text-align: right; margin-left: auto;
}

/* La leyenda de la barra de la cuenta: cuánto va pagado a la izquierda y
   el anticipo bajo su propia marca. Una sola línea: antes eran dos hitos
   que repetían lo que el pie de la tarjeta ya dice. El rótulo se sale del
   marco solo con un anticipo por debajo del ~10%, que ninguna convocatoria
   usa; si alguna lo hiciera, habría que acotarlo aquí. */
.progreso-leyenda {
  position: relative; margin: 8px 0 0; font-size: 13px; color: var(--gris-700);
}
.progreso-leyenda__marca {
  position: absolute; top: 0; transform: translateX(-50%); white-space: nowrap;
}
.progreso-leyenda strong { color: var(--tinta); font-variant-numeric: tabular-nums; }

/* Los dos compromisos con fecha, en el pie de la misma tarjeta. Estaban
   en dos sitios —el importe en la rejilla del desglose y la fecha en una
   tarjeta «Fechas clave» más abajo—, y son una sola frase: *tanto, para
   tal día*. Separados, había que recordar una cifra mientras se buscaba
   la otra.

   El filete que los parte es el mismo de `.section-head`: aquí no hace
   falta un componente nuevo, hace falta que se lea como el resto. */
.compromisos {
  margin: 0; padding: 16px 26px 18px;
  background: var(--gris-050); border-top: 1px solid var(--gris-200);
  display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 14px 32px;
}
.compromiso { min-width: 0; }
.compromiso dt {
  font-size: 11.5px; font-weight: 700; text-transform: uppercase;
  letter-spacing: .07em; color: var(--gris-500);
}
.compromiso dd { margin: 3px 0 0; }
.compromiso-monto {
  font-size: 18px; font-weight: 700;
  font-variant-numeric: tabular-nums; color: var(--tinta);
}
.compromiso-plazo { font-size: 13px; color: var(--gris-700); }
.compromiso-plazo.es-cumplido { color: var(--ok-600); font-weight: 600; }

/* La cotización de la selección (`U4`), como un recibo: concepto a la
   izquierda, importe a la derecha y el total aparte, más grande. Una lista
   de definiciones y no una tabla: son pares, y así en un teléfono no hay
   columnas que se salgan. */
.cotizacion { margin: 0; }
.cotizacion-renglon {
  display: flex; justify-content: space-between; align-items: baseline; gap: 16px;
  padding: 10px 0; border-bottom: 1px solid var(--gris-200);
}
.cotizacion-renglon dt { font-size: 14px; color: var(--tinta); }
.cotizacion-renglon dt .hint { margin-left: 6px; font-size: 13px; color: var(--gris-500); }
.cotizacion-renglon dd {
  margin: 0; font-size: 15px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* El descuento sin verde: `--ok-600` sobre blanco da 4.38:1, bajo AA a
   este tamaño, y el signo menos ya dice lo que es. */
/* El subtotal cierra la lista de espacios: raya más marcada encima y en
   negritas, para que se lea como la suma de lo de arriba. */
.cotizacion-renglon.es-subtotal { border-top: 2px solid var(--gris-300); }
.cotizacion-renglon.es-subtotal dt, .cotizacion-renglon.es-subtotal dd { font-weight: 700; }
.cotizacion-renglon.es-total { border-bottom: none; padding-top: 14px; }
.cotizacion-renglon.es-total dt { font-weight: 700; font-size: 16px; }
.cotizacion-renglon.es-total dd {
  font-size: 24px; font-weight: 800; color: var(--color-azul-institucional);
}
.cotizacion-nota { margin: 14px 0 0; }
@media (max-width: 920px) {
  .cotizacion-renglon dt .hint { display: block; margin: 2px 0 0; }
}

@media (max-width: 920px) {
  .estado-cuenta-cabecera { flex-direction: column; }
  .estado-cuenta-accion { align-items: flex-start; text-align: left; margin-left: 0; }
  .estado-cuenta-monto { font-size: 28px; }
}

/* La identidad de la reserva, encima de las pestañas: es de las tres y
   no del resumen. Era una `.section-card` con cabecera y sin cuerpo, o
   sea una barra gris con dos bordes pegados y nada debajo. */
.reserva-identidad {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 14px;
  margin-bottom: 16px;
}
.reserva-identidad h2 {
  margin: 0; font-size: 17px; color: var(--color-azul-institucional-enfoque);
  font-variant-numeric: tabular-nums;
}
.reserva-identidad .hint { margin-left: auto; }

/* Los datos bancarios, tal como los captura la convocatoria. `pre-wrap`
   respeta los saltos de línea de un CLABE escrito en su renglón. */
.instrucciones-pago { white-space: pre-wrap; margin: 0; }
.es-vencida { color: var(--err-600); }

/* Un aviso que además ofrece la acción — "te aceptamos" con su botón.
   `margin-left: auto` lo manda al final del renglón de lectura, que es
   donde la ley de Fitts lo quiere. */
.note .btn { flex: 0 0 auto; align-self: center; margin-left: auto; }

/* Los datos bancarios (STD · CU-STD-015). Campo por campo y no un párrafo:
   se copian de uno en uno frente a la app del banco, y una CLABE dentro de
   una frase es imposible de seleccionar sin arrastrar de más.

   `tabular-nums` en los números por lo mismo que en el desglose: una cuenta
   con cifras de ancho variable no se puede cotejar con el dedo. */
.datos-bancarios {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 4px 18px; margin: 14px 0 0;
  background: var(--gris-050); border: 1px solid var(--gris-200);
  border-radius: var(--radio-sm); padding: 14px 16px;
}
.datos-bancarios .dato { padding: 6px 0; min-width: 0; }
.datos-bancarios dt {
  font-size: 12px; color: var(--gris-500);
  text-transform: uppercase; letter-spacing: .05em;
}
/* El valor y su botón de copiar en el mismo renglón: el botón pertenece
   al dato, no al hueco de debajo. */
.datos-bancarios dd {
  margin: 2px 0 0; font-weight: 600; color: var(--color-azul-institucional);
  display: flex; align-items: center; gap: 10px;
}
/* Una CLABE no cabe en pantalla chica y no se puede partir por donde sea:
   se parte por cualquier carácter antes que salirse del marco. El
   `min-width: 0` es lo que deja encogerse a un hijo de flex —sin él, el
   valor empuja al botón fuera de la tarjeta en vez de partirse—. */
.datos-bancarios dd .valor { min-width: 0; overflow-wrap: anywhere; }
.datos-bancarios dd.es-numero { font-variant-numeric: tabular-nums; }

/* Copiar un dato al portapapeles (STD · CU-STD-015).

   No es un `.btn`: seis ghost de 40 px, uno por campo, competirían con
   «Registrar un pago», que es la acción de esta pantalla. Queda chico a la
   vista y grande al dedo — el `::after` estira el área pulsable a ~40 px
   sin mover nada de sitio (Fitts, `filey-ui-componentes` §7).

   Nace con `hidden` y lo descubre `copiable` (`filey.js`) solo si hay
   portapapeles; de esconderlo se encarga la regla global de `[hidden]`. */
/* Al final del renglón y sin encogerse: los seis botones quedan alineados
   entre sí, que es lo que permite ir bajando por la columna copiando. */
.datos-bancarios dd .btn-copiar { flex: 0 0 auto; margin-left: auto; }

.btn-copiar {
  position: relative;
  padding: 5px 10px; font: 600 13px/1.2 var(--font-filey);
  color: var(--azul-600); background: var(--color-blanco);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  cursor: pointer;
}
.btn-copiar::after { content: ''; position: absolute; inset: -8px; }
.btn-copiar:hover, .btn-copiar:focus-visible {
  border-color: var(--azul-600); background: var(--azul-050);
}
/* Que se copió lo dice la palabra; el borde solo acompaña. En verde el
   texto se quedaría en 4.38:1 sobre blanco, que es justo la deuda que
   `filey-identidad` §5 tiene medida y abierta: no se le añade otra. */
.btn-copiar.es-copiado { border-color: var(--ok-600); }

/* A10 · las dos mitades de la configuración (STD · CU-STD-034).
   Lo que cuesta a la izquierda, dónde se paga a la derecha. Se apila en
   una columna con el mismo breakpoint que el resto del sistema. */
.ajustes-columnas {
  display: grid; grid-template-columns: 1fr 1fr; gap: 0 22px;
  align-items: start;
}
@media (max-width: 920px) {
  .ajustes-columnas { grid-template-columns: 1fr; }
}

/* A4 · «Plazos y estado» (STD · CU-STD-035 y CU-STD-036).

   La tarjeta tiene dos plazos y un cierre, y cada uno es un bloque con
   su nombre, el estado vigente de ese plazo y su propio botón. Antes
   eran tres formularios apilados, sin más distintivo que la etiqueta del
   campo y con el botón de cada uno mandado al extremo derecho por
   `.form-actions`: la caja y el botón que la guarda acababan en esquinas
   opuestas, con una línea idéntica entre cada par. Escribir en un
   selector y pulsar el botón del otro era el camino natural, y así se
   reportó (`PF-STD-035`).

   Sin `.form-actions`, entonces: el botón cuelga del campo al que
   pertenece, a la izquierda y pegado a él. La separación entre bloques
   la lleva el propio bloque. */
.plazo { padding: 20px 0; border-top: 1px solid var(--gris-200); }
.plazo:first-child { padding-top: 0; border-top: none; }
.plazo > h4 {
  margin: 0 0 4px; font-size: 15px; font-weight: 700; color: var(--tinta);
}
.plazo__estado { margin: 0 0 14px; font-size: 14px; color: var(--gris-500); }
.plazo__estado--vencido { color: var(--err-600); font-weight: 600; }
/* Una fecha son diez caracteres: una caja del ancho de la tarjeta invita
   a buscarle contenido que no tiene. Solo la de los plazos — el motivo
   de la cancelación es un texto y sí quiere el ancho entero. */
.plazo > form > .field { max-width: 260px; margin-bottom: 12px; }
/* La línea que separa el cierre es la misma, pero el bloque respira más:
   no es un plazo más de la lista, es lo que no se deshace. */
.plazo--cierre { padding-top: 24px; }

/* Un sello y la carta que autoriza a representarlo, en el mismo renglón
   (STD · RN-17). Es la relación que la pantalla tiene que dejar ver: en
   una lista de documentos todas las cartas se llaman igual.

   El nombre a la izquierda y su carta a la derecha, separados por la
   línea de la fila: con nombres de largo desigual, pegarlos uno a otro
   hace perder de vista a cuál pertenece cada enlace. */
.lista-sellos { list-style: none; margin: 0; padding: 0; }
.lista-sellos li {
  display: flex; align-items: baseline; gap: 10px;
  padding: 8px 0; border-bottom: 1px solid var(--gris-100);
}
.lista-sellos li:last-child { border-bottom: none; }
.lista-sellos .sello-nombre { font-weight: 600; }
/* La carta que ampara a toda la lista, debajo de ella (desde el
   30-09-2026 es una sola por editorial). */
.carta-sellos {
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px;
  margin: 12px 0 0; padding-top: 10px; border-top: 1px solid var(--gris-200);
}
/* Lo de la derecha es el enlace o el distintivo de «sin carta»: los dos
   se alinean al mismo sitio para poder recorrer la columna de un vistazo. */
.lista-sellos li > :last-child { margin-left: auto; text-align: right; }

/* ═══ La barra de filtros de una cola (STD · A1, A3) ═══════════
   `.chips`, `.chip` y `.searchbox` son del prototipo de EVT
   (`prototipo/EVT/styles.css`), donde ya filtran la cola de propuestas.
   Se portan tal cual —mismos valores, mismos tokens— en vez de escribir
   otra barra: dos paneles del mismo sistema no pueden filtrar de dos
   formas distintas. */

/* El buscador **nunca** baja de línea: `nowrap` en el contenedor y un
   ancho fijo para la caja. Lo que cede son los chips, que envuelven
   dentro de su propia columna.

   Antes cedía la caja —`flex-wrap: wrap` y `flex: 1`— y eso hacía que la
   barra se comportara distinto en cada pantalla: con cinco chips
   (solicitudes) entraba todo en un renglón y con seis (reservas) el
   buscador se iba solo al siguiente. La misma barra no puede tener dos
   formas según cuántos estados tenga la cola. */
.filtros {
  display: flex; gap: 16px 20px; align-items: center;
  justify-content: space-between; flex-wrap: nowrap; margin-bottom: 10px;
}
/* `min-width: 0` para que la columna de chips pueda encogerse por
   debajo de su contenido: sin esto un elemento flexible se niega a bajar
   de su ancho mínimo y empuja al buscador fuera del marco. */
.chips {
  display: flex; flex-wrap: wrap; gap: 9px; align-items: center;
  flex: 1 1 auto; min-width: 0;
}
.chip {
  background: var(--color-blanco); border: 1.5px solid var(--gris-300);
  color: var(--gris-700); padding: 8px 15px; border-radius: 999px;
  font-size: 13.5px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  /* Una etiqueta partida en dos renglones dentro de la píldora la
     deforma. Al estrecharse, la fila rompe **entre** chips y no dentro
     de uno. */
  white-space: nowrap;
}
.chip:hover { border-color: var(--azul-600); color: var(--color-azul-institucional); text-decoration: none; }
.chip:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px; }
.chip.is-active {
  background: var(--color-azul-institucional);
  border-color: var(--color-azul-institucional);
  color: var(--color-blanco);
  cursor: default;
}
/* El conteo en un óvalo dentro del chip. Translúcido y no un token de
   color: tiene que funcionar sobre el chip blanco y sobre el azul. */
.chip .count {
  background: rgba(0,0,0,.08); border-radius: 999px; padding: 0 7px;
  font-size: 12px; font-variant-numeric: tabular-nums;
}
.chip.is-active .count { background: rgba(255,255,255,.22); }

.searchbox {
  display: flex; align-items: center; gap: 8px; background: var(--color-blanco);
  border: 1.5px solid var(--gris-300); border-radius: var(--radio-sm);
  padding: 5px 6px 5px 14px;
  /* Ni crece ni se encoge: es el ancla derecha de la barra, y su tamaño
     no puede depender de cuántos estados tenga la cola. */
  flex: 0 0 320px;
}
/* La caja es el marco; el control de dentro no lleva el suyo. Se escribe
   con el tipo en el selector para ganarle a la regla base de los campos
   de texto sin depender del orden del archivo. */
.searchbox input[type="search"] {
  border: none; padding: 0; font-size: 14px; background: transparent;
  flex: 1; min-width: 0;
}
.searchbox input[type="search"]:focus { outline: none; box-shadow: none; }
.searchbox:focus-within { border-color: var(--azul-600); box-shadow: 0 0 0 3px var(--azul-100); }
.searchbox .ico { color: var(--gris-500); font-size: 14px; }

/* Cuántas se están viendo. Va pegado a la barra y separado de la tabla:
   es la respuesta del filtro, no un encabezado de la lista. */
.filtros-cuenta {
  margin: 0 0 18px; font-size: 13px; color: var(--gris-500);
}
.filtros-cuenta strong { color: var(--tinta); font-variant-numeric: tabular-nums; }

/* La región que se intercambia al filtrar, mientras se intercambia.
   HTMX le pone `htmx-request` a lo que señale `hx-indicator`, y las
   colas de `STD` señalan la región entera: lo que cambia es la lista,
   así que es la lista la que tiene que decir que está cambiando.

   Atenuar y no tapar. Un velo o un giro sobre una tabla que tarda
   ochenta milisegundos parpadea más de lo que informa; bajar la opacidad
   se lee como «esto ya no es lo definitivo» sin llamar la atención. Y la
   transición es corta a propósito: más larga y el atenuado sigue
   entrando cuando la respuesta ya llegó. */
.cola { transition: opacity .12s ease; }
.cola.htmx-request { opacity: .55; }

@media (prefers-reduced-motion: reduce) {
  .cola { transition: none; }
}

@media (max-width: 920px) {
  /* En pantalla chica sí se apila, y entonces la caja ocupa el ancho:
     320px fijos al lado de dos renglones de chips dejaría los tres
     controles apretados. */
  .filtros { flex-wrap: wrap; }
  .searchbox { flex: 1 1 100%; }
}

/* ═══ Paginación de una cola (componentes/paginacion.html) ═════
   Capa común desde que nace: la estrena `EVT` (`H-02`) y las cuatro
   colas de `STD` van a pedir la misma barra.

   Va **debajo** de la tabla y no encima: quien llega al final es quien
   quiere la página siguiente, y arriba compite con los filtros, que son
   el control que de verdad recorta. La cuenta a la izquierda y los dos
   botones a la derecha, que es el reparto que ya usa la barra de
   filtros. */
.paginacion {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin-top: 18px;
}
.paginacion-cuenta { margin: 0; font-size: 13px; color: var(--gris-500); }
/* Los números no bailan al cambiar de página: son cifras que se leen una
   debajo de otra al pulsar varias veces seguidas. */
.paginacion-cuenta,
.paginacion-posicion { font-variant-numeric: tabular-nums; }
.paginacion-controles { display: flex; align-items: center; gap: 10px; }
.paginacion-posicion { font-size: 13px; color: var(--gris-500); }

/* El extremo de la lista. Mantiene el sitio del botón —si se escondiera,
   el otro saltaría de posición justo al llegar al final— y se apaga sin
   ofrecer nada que pulsar. `.side-link.is-disabled` ya existía para la
   barra lateral; esto es lo mismo para un botón. */
.btn.is-disabled {
  color: var(--gris-300); border-color: var(--gris-200);
  cursor: not-allowed; pointer-events: none;
}

@media (max-width: 560px) {
  /* Apilada, la cuenta va arriba: es la que se lee, y los botones caen
     donde el pulgar los alcanza. */
  .paginacion { flex-direction: column; align-items: stretch; }
}

/* ═══ Modal (STD · A5, CU-STD-018) ═════════════════════════════
   Portado de `prototipo/EVT/styles.css`, donde es el diálogo del
   dictamen. Mismos valores y mismos tokens: dos módulos del mismo panel
   no pueden abrir dos modales distintos.

   Lo que htmx deja dentro de `#modal` es el velo entero, así que el
   hueco vacío no ocupa nada mientras no hay nada abierto. */
.modal-back {
  position: fixed; inset: 0; background: rgba(16,26,48,.55);
  display: flex; align-items: center; justify-content: center;
  padding: 24px; z-index: 60;
}
.modal-back[hidden] { display: none; }
.modal {
  background: var(--color-blanco); border-radius: var(--radio);
  box-shadow: var(--sombra-lg); width: 100%; max-width: 560px;
  /* `dvh` y no `vh`: es el mismo motivo por el que `body` mide contra
     `html` y no contra `100vh` (ver arriba). En un teléfono con la barra
     de direcciones a la vista, `100vh` es más alto que lo visible, así
     que el pie del modal —donde están los botones— queda debajo del
     navegador y no se alcanza. `dvh` mide lo que de verdad se ve. */
  max-height: calc(100dvh - 48px);
  /* Las dos, no solo la vertical: si el cuerpo trae algo que no encoge
     —una tabla, un correo largo— sin esto se sale del marco en vez de
     desplazarse dentro. */
  overflow: auto;
}
.modal-head { padding: 20px 24px; border-bottom: 1px solid var(--gris-200); }
.modal-head h3 { margin: 0; font-size: 19px; color: var(--color-azul-institucional-enfoque); }
.modal-head p { margin: 4px 0 0; font-size: 13.5px; color: var(--gris-500); }
.modal-body { padding: 22px 24px; }
.modal-body .info-grid { margin-bottom: 16px; }
.modal-foot {
  padding: 16px 24px; border-top: 1px solid var(--gris-200);
  display: flex; gap: 10px; justify-content: flex-end; align-items: center;
  background: var(--gris-050);
}
/* El pie del rechazo lleva un campo, así que se apila en vez de alinear
   a la derecha; sus dos botones sí vuelven a la fila. */
.modal-foot.es-rechazo { display: block; }
.modal-foot .field { margin-bottom: 12px; }
.modal-acciones { display: flex; gap: 10px; justify-content: flex-end; }

/* El mismo cuerpo, sin velo: es la pantalla suelta, la que sale al abrir
   el enlace tal cual. Se le da el marco que el velo no está poniendo. */
.modal-suelto .modal {
  max-width: 620px; border: 1px solid var(--gris-200);
  box-shadow: var(--sombra-sm); max-height: none;
}

/* ═══ Las Bases de participación (STD · CU-STD-001) ════════════
   El diálogo que abre la casilla de aceptación de U1. Dos cosas lo
   separan de los demás modales del proyecto, y las dos salen de que su
   contenido **no lo escribe el código**: lo pega quien administra desde
   el admin de la edición, y son varias páginas.

   Más ancho que los 560px de siempre: un modal de diálogo enmarca una
   decisión de dos datos, y esto es un documento que se lee. Por debajo
   de 760px el `width: 100%` del modal común ya manda, así que en un
   teléfono no cambia nada. */
.modal.es-bases { max-width: 720px; }
.modal-suelto .modal.es-bases { max-width: 760px; }

/* Lo que se desplaza es el **cuerpo**, no el diálogo entero. En los
   demás modales da igual —son media pantalla de datos— y aquí no:
   desplazando el diálogo, el botón de cerrar se va hacia arriba a la
   segunda página del documento, y quien llega al final de unas bases
   largas no tiene con qué salir. El `min-height: 0` es lo que permite
   que un hijo de un contenedor flex encoja por debajo de su contenido;
   sin él el cuerpo no se desplaza, crece. */
.modal.es-bases { display: flex; flex-direction: column; }
.modal.es-bases > .modal-head, .modal.es-bases > .modal-foot { flex: none; }
.modal.es-bases > .modal-body { overflow: auto; min-height: 0; }
/* Suelta no hay nada que retener: la pantalla se desplaza como
   cualquier otra y el pie llega a su turno. */
.modal-suelto .modal.es-bases { display: block; }

/* La ✕ de cerrar sin aceptar, en la esquina del encabezado. El
   encabezado le reserva su hueco para que un nombre de convocatoria
   largo no pase por debajo. */
.modal.es-bases > .modal-head { position: relative; padding-right: 60px; }
.modal.es-bases .modal-cerrar { top: 14px; right: 14px; }
/* El foco del cuerpo se ve: es lo que se desplaza con el teclado para
   llegar al final, y sin anillo no se sabe que lo tiene. */
.modal.es-bases > .modal-body:focus-visible {
  outline: 2px solid var(--color-azul-institucional); outline-offset: -2px;
}
/* El pie lleva el porqué del botón apagado a su izquierda; en un
   teléfono no caben los dos en una fila y el aviso sube encima. */
.modal.es-bases > .modal-foot { flex-wrap: wrap; }
.bases-pendiente {
  margin: 0 auto 0 0; font-size: 13px; line-height: 1.45; color: var(--gris-700);
}

/* ═══ Corregir el expediente (STD · A7, CU-STD-042) ══════════
   La ficha entera del expositor dentro de un diálogo: cuatro tarjetas y
   treinta campos. Comparte con las bases lo que importa —más ancho que
   los 560px de un diálogo de decisión, y lo que se desplaza es el cuerpo
   para que «Guardar cambios» y la ✕ no se vayan con la primera tarjeta—.
   Más ancho que las bases porque aquí van dos columnas de campos, no
   un texto que se lee.

   El hijo flexible es el `<form>`, no el diálogo: envuelve encabezado,
   cuerpo y pie para que el botón del pie envíe. */
.modal.es-ficha { max-width: 880px; display: flex; flex-direction: column; overflow: hidden; }
.es-ficha > .ficha-form { display: flex; flex-direction: column; flex: 1 1 auto; min-height: 0; }
.es-ficha .modal-head, .es-ficha .modal-foot { flex: none; }
/* El cuerpo en gris para que las tarjetas blancas de la ficha se lean
   como tarjetas y no como un formulario sin costuras. */
.es-ficha .modal-body { overflow: auto; min-height: 0; background: var(--gris-050); }
.es-ficha .modal-head { position: relative; padding-right: 60px; }
.es-ficha .modal-cerrar { top: 14px; right: 14px; }
/* El título recibe el foco al abrir (para el lector de pantalla), pero
   no es un control: un anillo ahí parecería un botón. */
.es-ficha .modal-head h3:focus { outline: none; }
.es-ficha .section-card { box-shadow: none; margin-bottom: 16px; }
.es-ficha .section-card:last-child { margin-bottom: 0; }
/* Suelta se desplaza la pantalla, como las bases. */
.modal-suelto .modal.es-ficha { max-width: 920px; display: block; overflow: visible; }
.modal-suelto .es-ficha > .ficha-form { display: block; }
/* Por qué Escape o el clic fuera no cerraron: a la izquierda del pie, que
   es donde se mira antes de pulsar «Cancelar». En teléfono sube encima. */
.es-ficha .modal-foot { flex-wrap: wrap; }
.ficha-sin-guardar {
  margin: 0 auto 0 0; font-size: 13px; line-height: 1.45; font-weight: 600;
  color: var(--gris-700);
}

/* El texto con formato de un campo de administración. La tipografía se
   acota aquí en vez de confiar en la que traiga pegada: lo que pasa por
   `comun/html_admitido.py` conserva las etiquetas y **ningún** atributo
   de estilo, justamente para que el aspecto lo decida esta hoja.

   Es genérico a propósito —no `.bases-…`—: el siguiente campo de este
   tipo se pinta con esto y no con una copia. */
.prosa { font-size: 15px; line-height: 1.65; color: var(--tinta); }
.prosa > :first-child { margin-top: 0; }
.prosa > :last-child { margin-bottom: 0; }
.prosa p { margin: 0 0 12px; }
.prosa h3, .prosa h4, .prosa h5 {
  margin: 22px 0 8px; color: var(--color-azul-texto); line-height: 1.35;
}
.prosa h3 { font-size: 17px; font-weight: 700; }
.prosa h4 { font-size: 15.5px; font-weight: 700; }
.prosa h5 { font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: .5px; }
.prosa ul, .prosa ol { margin: 0 0 12px; padding-left: 22px; }
.prosa li { margin-bottom: 6px; }
.prosa a { text-decoration: underline; }
.prosa blockquote {
  margin: 0 0 12px; padding: 2px 0 2px 14px;
  border-left: 3px solid var(--oro-200); color: var(--gris-700);
}
.prosa hr { margin: 18px 0; border: 0; border-top: 1px solid var(--gris-200); }
/* Una tabla pegada de un documento no trae anchos, así que se reparte
   sola; `fixed` la mantiene dentro del diálogo en vez de estirarla con
   la celda más larga. */
.prosa table {
  width: 100%; table-layout: fixed; border-collapse: collapse;
  margin: 0 0 14px; font-size: 14px;
}
.prosa th, .prosa td {
  border: 1px solid var(--gris-200); padding: 8px 10px;
  text-align: left; vertical-align: top; word-break: break-word;
}
.prosa th { background: var(--gris-050); font-weight: 700; }

/* El nombre de las bases, dentro de la casilla de aceptación. Hereda el
   color de la nota en vez de ponerse azul: distinguir un enlace **solo**
   por color no vale (`filey-identidad` §4), y el subrayado lo dice sin
   estrenar una pareja de contraste que nadie ha medido — el dorado de
   `.note-gold` ya está en la deuda abierta. */
.nota-elegible a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }

/* ═══ El modal de validar un abono (STD · CU-STD-018) ══════════
   Tres piezas sobre el modal común, y cada una responde a un paso del
   caso de uso: el monto que se coteja, el comprobante que hay que abrir
   y lo que la validación le hará a la reserva. */

/* El monto como héroe. Es el mismo tratamiento de número grande que las
   tarjetas del resumen —30px/800, azul de enfoque, cifras de ancho
   fijo—: no se inventa uno nuevo para la misma figura. */
.abono-cabecera { display: flex; align-items: flex-start; gap: 16px; }
.abono-cabecera > div { flex: 1; min-width: 0; }
.abono-cabecera .hint {
  display: block; text-transform: uppercase; letter-spacing: .6px;
  font-weight: 700; font-size: 12.5px;
}
.abono-monto {
  display: block; font-size: 30px; font-weight: 800; line-height: 1.1;
  color: var(--color-azul-institucional-enfoque);
  font-variant-numeric: tabular-nums;
}

/* El comprobante es el trabajo (CU-STD-018 pasos 3 y 4), así que es un
   blanco grande y no un enlace entre etiquetas. Altura holgada por la
   ley de Fitts: se pulsa una vez por cada abono de la cola. */
.comprobante {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 16px; margin-bottom: 18px;
  border: 1.5px solid var(--azul-100); border-radius: var(--radio-sm);
  background: var(--azul-050); color: var(--color-azul-institucional);
}
.comprobante:hover {
  border-color: var(--azul-600); background: var(--color-blanco);
  text-decoration: none;
}
.comprobante:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px; }
.comprobante-ico { font-size: 22px; line-height: 1; }
.comprobante-texto { display: flex; flex-direction: column; min-width: 0; }
.comprobante-texto .hint { overflow-wrap: anywhere; }
.comprobante-flecha { margin-left: auto; font-size: 16px; }

/* La proyección: qué le hace este abono a la reserva. Reusa la barra de
   progreso de la cuenta del expositor y le añade un segundo tramo.

   El ámbar del tramo nuevo **no es decoración**: es el color de
   «pendiente de validación» en todo el sistema, y al validar ese trozo
   pasa a verde. Con un solo color no se distinguiría lo cobrado de lo
   que está por cobrar, que es justo la decisión. */
.proyeccion {
  border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  background: var(--gris-050); padding: 14px 16px;
}
/* Los dos tramos en fila; la marca del 50% sigue absoluta y no entra en
   el reparto. */
.proyeccion .progreso-barra { display: flex; }
.progreso-suma { display: block; height: 100%; background: var(--warn-600); }
.proyeccion-efecto { margin: 10px 0 0; font-size: 13.5px; color: var(--gris-700); }


/* ===================== EVT · captura de propuesta =====================
   Portado de `prototipo/EVT/aplicantes/formulario.html` y de
   `prototipo/EVT/styles.css` con los mismos nombres de clase, que es lo
   que mantiene reconciliables las dos hojas.

   Tres referencias del prototipo se corrigieron al copiar, porque allá
   son tokens rotos que `check-ui.sh` ya marca (E1): `--blanco`,
   `--azul-900`, `--azul-400` y `--oro-600` no existen. Aquí van con su
   nombre bueno; copiar el error lo habría hecho crecer. */

.breadcrumb { font-size: 13px; color: var(--gris-500); margin-bottom: 14px; }
.breadcrumb a { color: var(--gris-500); }
.breadcrumb span { color: var(--gris-300); margin: 0 6px; }


/* Datos que vienen de la cuenta y no se capturan aquí. */
.field-prefill { position: relative; }
.field-prefill .tag-auto {
  position: absolute; right: 10px; top: 34px; font-size: 11px;
  background: var(--azul-100); color: var(--azul-600);
  padding: 2px 8px; border-radius: 999px; font-weight: 600;
}

/* Selector de tipo de actividad. Son botones de un formulario GET: elegir
   uno recarga la pantalla con sus campos, y con htmx cambia solo esa
   sección. Ver `apps/eventos/views.py`. */
.tipo-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.tipo-opt {
  border: 1.5px solid var(--gris-300); background: var(--color-blanco);
  border-radius: var(--radio-sm); padding: 16px 12px; cursor: pointer;
  text-align: center; font-family: inherit; font-size: 14px; font-weight: 600;
  color: var(--gris-700); transition: all .15s ease;
}
.tipo-opt .em { display: block; font-size: 24px; margin-bottom: 8px; }
.tipo-opt:hover { border-color: var(--azul-600); background: var(--azul-050); }
.tipo-opt.is-active {
  border-color: var(--color-dorado-encabezado); background: var(--oro-050);
  color: var(--color-azul-institucional-enfoque); box-shadow: 0 0 0 3px var(--oro-050);
}
@media (max-width: 720px) { .tipo-grid { grid-template-columns: repeat(2, 1fr); } }

/* Selección múltiple de público. */
.check-chips { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 8px; }
.check-chip { display: inline-flex; align-items: center; gap: 8px; cursor: pointer; }
.check-chip input[type="checkbox"] { position: absolute; opacity: 0; pointer-events: none; }
.check-chip .chip {
  display: inline-flex; align-items: center; gap: 6px;
  border: 1.5px solid var(--gris-300); border-radius: 999px;
  padding: 6px 14px; font-size: 14px; font-weight: 500;
  color: var(--gris-700); background: var(--color-blanco);
  transition: all .15s ease; cursor: pointer; user-select: none;
}
.check-chip input:checked + .chip {
  border-color: var(--azul-600); background: var(--azul-050);
  color: var(--color-azul-institucional-enfoque); font-weight: 600;
}
.check-chip input:focus-visible + .chip { outline: 2px solid var(--azul-600); outline-offset: 2px; }
.check-chip .chip:hover { border-color: var(--azul-600); background: var(--azul-050); }

/* El grupo de personas entero —sus filas, el botón de añadir y los avisos
   que cuelgan de él— es lo que se separa de lo que venga después, que casi
   siempre es otro grupo: los autores y luego los presentadores.

   La separación **no** puede colgar del botón. Es lo último visible casi
   siempre, pero no cuando el grupo llegó a su máximo y el botón se retira,
   ni cuando debajo sale el aviso de por qué no se puede añadir a nadie. Con
   el margen aquí, la distancia es la misma en los tres casos.

   Y es mayor que los 12 px que separan a dos personas **del mismo** grupo,
   a propósito: lo que hay que poder ver de un vistazo es dónde acaba «los
   autores» y empieza «los presentadores». Con 0, que es lo que había, el
   botón de añadir quedaba pegado al encabezado del grupo siguiente y los
   dos se leían como una sola sección. */
.evt-personas { margin-bottom: 24px; }
.evt-personas:last-child { margin-bottom: 0; }

/* Cada persona de la actividad, en su fila. */
.evt-persona {
  border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  background: var(--gris-050); padding: 14px 16px; margin-bottom: 12px;
}
.evt-persona__head { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-bottom: 12px; }
.evt-persona__n {
  font-size: 11.5px; font-weight: 800; letter-spacing: .6px; text-transform: uppercase;
  color: var(--color-azul-institucional);
}
.evt-persona .field:last-child { margin-bottom: 0; }
.evt-persona[hidden] { display: none; }
.evt-persona__quitar {
  margin-left: auto; border: none; background: none; padding: 0; cursor: pointer;
  font-family: inherit; font-size: 13px; font-weight: 600; color: var(--err-600);
  /* Ley de Fitts: el texto mide 13 px de alto y es un objetivo minúsculo, sobre
     todo en táctil. El pseudo-elemento agranda el área pulsable sin meter un
     nodo más ni mover nada de sitio. */
  position: relative;
}
.evt-persona__quitar::after { content: ''; position: absolute; inset: -12px; }

/* Botón de revelar la siguiente persona. Desaparece al llegar al máximo. */
.evt-agregar {
  width: 100%; padding: 10px 16px; cursor: pointer; font-family: inherit;
  border: 1.5px dashed var(--gris-300); border-radius: var(--radio-sm);
  background: var(--color-blanco); font-size: 13.5px; font-weight: 600; color: var(--azul-600);
}
.evt-agregar:hover { border-color: var(--azul-600); background: var(--azul-050); }
.evt-agregar[hidden] { display: none; }

/* Un control que todavía no toca usar se apaga —no se esconde, para que
   se vea que existe— y siempre va acompañado del aviso que dice qué
   falta: el gris por sí solo se lee como una avería. Es `readonly` y no
   `disabled` (ver la cascada en `filey.js`): lo que tenga escrito sigue
   viajando al servidor, que es quien lo rechaza si se quedó sin nombre. */
.evt-persona textarea[readonly] {
  background: var(--gris-100); border-color: var(--gris-200); color: var(--gris-500);
}
.evt-agregar:disabled,
.evt-agregar:disabled:hover {
  background: var(--gris-050); border-color: var(--gris-200); color: var(--gris-500);
}
.evt-bloqueo { margin: 6px 0 0; font-size: 12.5px; color: var(--gris-500); }

/* Casilla suelta: «es de la UADY» y «participará». Va como
   `label.evt-check` porque dentro de un `.field` compite con
   `.field > label`, que la volvería un bloque en negritas. */
label.evt-check {
  display: inline-flex; align-items: center; gap: 8px; cursor: pointer;
  font-size: 13.5px; font-weight: 500; color: var(--gris-700);
}
.evt-check input {
  width: 16px; height: 16px; margin: 0; cursor: pointer;
  accent-color: var(--color-azul-institucional);
}
.evt-institucion { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; }
/* `> input` y no `input` a secas: la casilla de la UADY vive dentro
   del label, y con el selector amplio heredaba el `flex: 1` del campo
   de texto — se estiraba y empujaba su propia etiqueta lejos. */
.evt-institucion > input { flex: 1; min-width: 12rem; }

/* Aceptación expresa de las bases. */
.acepta { display: flex; gap: 14px; align-items: flex-start; cursor: pointer; }
.acepta input[type="checkbox"] {
  flex: 0 0 20px; width: 20px; height: 20px; margin: 1px 0 0;
  accent-color: var(--color-azul-institucional); cursor: pointer;
}
.acepta-txt { font-size: 14px; color: var(--gris-700); line-height: 1.55; }
.acepta-txt strong { color: var(--color-azul-institucional-enfoque); }
.acepta-txt small { display: block; margin-top: 6px; font-size: 12.5px; color: var(--gris-500); }

/* La tarjeta de envío de la propuesta: el texto no es el rótulo de la
   casilla, porque lleva el enlace a las bases —un rótulo que lo
   envolviera marcaría la casilla al pulsar el enlace—. La casilla se
   marca solo desde su caja, con un área pulsable de 44 px alrededor de
   sus 20 px (Fitts, §7 de `filey-ui-componentes`), y el párrafo le da
   nombre por `aria-labelledby`. Los botones van debajo, en la misma
   tarjeta, y `.form-actions` ya trae la línea que los separa. */
.acepta-bases { cursor: default; margin-bottom: 12px; }
.acepta-caja { position: relative; display: flex; flex: 0 0 auto; cursor: pointer; }
.acepta-caja::after { content: ''; position: absolute; inset: -12px; }
.acepta-bases .acepta-txt { margin: 0; }
.acepta-txt a {
  color: var(--color-azul-institucional-enfoque); font-weight: 700;
  text-decoration: underline; text-underline-offset: 3px;
}
.acepta-txt a:hover, .acepta-txt a:focus-visible { color: var(--color-azul-institucional); }

/* ===================== Variante `tal` (talleres) =====================
   Copiadas de `prototipo/TAL/styles.css`, con sus nombres. La declaración
   bajo protesta va en la misma tarjeta que las bases y antes de ellas: la
   raya las separa porque son dos aceptaciones distintas. */
.tal-procedencia { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 4px; font-size: 13.5px; color: var(--gris-700); }
/* La procedencia es un dato, no un veredicto: va en neutro y igual para
   las tres (12-10-2026). Los colores de estado la leerían como permiso. */
.badge-procedencia { background: var(--gris-100); color: var(--gris-700); }
.tal-declaracion { border-bottom: 1px solid var(--gris-200); padding-bottom: 16px; margin-bottom: 16px; }
.acepta-txt ul { margin: 8px 0 0; padding-left: 20px; display: grid; gap: 4px; font-size: 13px; color: var(--gris-700); line-height: 1.45; }

/* Las bases de talleres son texto corrido con encabezados, y sus dos
   tarjetas de espacios (Ek Balam, salas de cine) no son el «sí cubre / no
   cubre» de `evt`: por eso todo va acotado a `.tal-bases`, y las reglas de
   `.bases-col` de arriba siguen siendo las de eventos. */
.tal-bases h4 { margin: 26px 0 12px; font-size: 14px; color: var(--color-azul-institucional-enfoque); }
.tal-bases h4:first-child { margin-top: 0; }
.tal-bases > p { margin: 0 0 10px; font-size: 14px; color: var(--gris-700); line-height: 1.55; }
.tal-bases ul { margin: 0 0 10px; padding-left: 20px; display: grid; gap: 6px; font-size: 14px; color: var(--gris-700); line-height: 1.5; }
.tal-bases .bases-col { background: var(--gris-050); }
.tal-bases .bases-col h4 { margin: 0 0 8px; font-size: 14px; letter-spacing: 0; text-transform: none; }
.tal-bases .bases-col p { margin: 0 0 8px; font-size: 13.5px; color: var(--gris-700); line-height: 1.5; }
.tal-bases .bases-col p:last-child { margin-bottom: 0; }
.tal-bases .cond-list li p { margin: 4px 0 0; }
.tal-nota { margin: 18px 0 0; }

/* ===================== Confirmación =====================
   Copiado de `prototipo/common/styles-base.css` línea 455, tal cual y
   con los mismos nombres de token. Es de `common` y no de un dominio:
   `VIS` ya lo usa en su propia confirmación, y `STD` lo usará. */
.confirm-card { text-align: center; padding: 48px 40px; }
.confirm-card .check {
  width: 76px; height: 76px; border-radius: 50%; margin: 0 auto 22px;
  background: var(--ok-050); color: var(--ok-600); display: grid; place-items: center; font-size: 38px;
}
.confirm-card h1 { margin: 0 0 8px; color: var(--color-azul-institucional-enfoque); }
.confirm-card p { color: var(--gris-500); margin: 0 auto 8px; max-width: 46ch; }
.folio-box {
  display: inline-flex; flex-direction: column; align-items: center; gap: 2px;
  background: var(--oro-050); border: 1.5px dashed var(--color-dorado-encabezado);
  padding: 16px 34px; border-radius: var(--radio); margin: 18px 0 26px;
}
.folio-box small { font-size: 12px; color: var(--color-dorado-700); text-transform: uppercase; letter-spacing: 1px; }
.folio-box strong { font-size: 26px; color: var(--color-azul-institucional-enfoque); letter-spacing: 1px; }

/* La fila recién enviada, en el listado de CU-EVT-003.
   Calcado de `prototipo/EVT/aplicantes/mis-propuestas.html`, que lo
   tenía en un `<style>` embebido: al portarlo baja a la hoja.

   `row-` y no `fila-` **a propósito**, aunque la regla 7 pida español:
   el nombre es el que ya usa el prototipo, y dos nombres para la misma
   clase es exactamente lo que vuelve irreconciliables las dos hojas.
   `.btn-row` ya está aquí por lo mismo. El día que se renombre, se
   renombra en los dos a la vez.

   El destello dura 1.6 s y termina en transparente, así que quien llega
   por su cuenta a la lista —sin venir del acuse— no ve nada raro. La
   pastilla «nueva» sí se queda: es lo que sigue diciendo cuál es
   después de que el color se apague, y el color solo nunca comunica un
   estado (`filey-identidad` §4). */
tr.row-nueva td { animation: filaEntra 1.6s ease; }
@keyframes filaEntra {
  0%   { background: var(--oro-050); }
  100% { background: transparent; }
}
/* Quien haya pedido no animar, no ve el destello: la pastilla ya dice
   lo mismo sin moverse. */
@media (prefers-reduced-motion: reduce) {
  tr.row-nueva td { animation: none; }
}
.pill-nueva {
  display: inline-block; margin-left: 8px; font-size: 10.5px; font-weight: 700;
  color: var(--color-azul-institucional); background: var(--oro-050);
  border: 1px solid var(--oro-200); border-radius: 999px; padding: 1px 8px; vertical-align: middle;
}

/* ===================== Adjuntos, en galería =====================
   NUEVO, sin contraparte en el prototipo: ahí la pantalla del aplicante
   no enseñaba los archivos.

   La rejilla fluye sola —`auto-fill` con un mínimo— en vez de fijar un
   número de columnas: son uno, dos o tres adjuntos según el tipo, y con
   `repeat(3, 1fr)` un adjunto solo salía en un tercio de ancho. */
.adjuntos { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 16px; }

/* El marco existe **siempre y con altura propia**, tenga o no imagen
   dentro. Es lo que evita que la página dé un salto cuando una imagen
   diferida termina de cargar, y lo que da un sitio reconocible al PDF y
   al archivo que no se pudo cargar. */
.adjunto__marco {
  display: grid; place-items: center; height: 150px; overflow: hidden;
  border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  background: var(--gris-050); text-decoration: none; position: relative;
}
.adjunto__marco:hover { border-color: var(--azul-600); }
.adjunto__marco img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* El aviso de "no se pudo cargar" y el del PDF viven dentro del marco y
   solo se ven cuando toca. */
.adjunto__aviso {
  /* El eje que manda el tamaño del icono. Vive aquí y no en `:root`
     porque solo lo usan las dos reglas de abajo: un token global para un
     valor de un componente es ruido en la paleta. */
  --adjunto-ico: 34px;
  display: grid; place-items: center; gap: 6px; text-align: center;
  padding: 12px; font-size: 12.5px; color: var(--gris-500); line-height: 1.4;
}
/* El `.ico` sigue sirviendo para un emoji —el clip del archivo que no se
   puede pintar— y para un SVG en línea. Los dos toman el mismo alto. */
.adjunto__aviso .ico { font-size: var(--adjunto-ico); line-height: 1; }
/* Aquí caben dos iconos —el del PDF (`viewBox` 24×24) y el clip
   (512×512)—, y los dos son cuadrados, así que el ancho es el alto. El
   `calc()` se escribe aunque dé 1: documenta el ratio y avisa el día que
   entre uno que no lo sea (`filey-ui-componentes` §8). */
.adjunto__aviso .ico-svg {
  display: block;
  height: var(--adjunto-ico);
  width: calc(var(--adjunto-ico) * 24 / 24);
}
/* Al apuntar al marco, el icono acompaña al borde. Es la única señal de
   que la tarjeta entera es pulsable: el texto no cambia. */
.adjunto__marco:hover .adjunto__aviso { color: var(--azul-600); }
.adjunto__marco.es-rota img { display: none; }
.adjunto__pie {
  margin: 8px 0 0; font-size: 12.5px; color: var(--gris-500); line-height: 1.45;
}
.adjunto__pie strong { display: block; color: var(--gris-700); font-weight: 600; }

/* ── El visor ──────────────────────────────────────────────────
   Un `<dialog>` y no un velo propio: `showModal()` ya trae el foco
   atrapado dentro, Escape, el `::backdrop` y el resto de la página
   marcada como inerte. Lo que hay que deshacer son sus estilos de
   fábrica —borde, relleno y un ancho que no es el de la imagen—. */
.visor {
  border: none; padding: 0; background: none; overflow: visible;
  max-width: none; max-height: none;
  /* `<dialog>` se centra con `margin: auto` sobre los cuatro lados, que
     es lo que hace el `inset: 0` de aquí abajo posible. */
  inset: 0; margin: auto;
}
.visor::backdrop { background: rgba(16,26,48,.78); }

/* Las bases encima del formulario (CU-EVT-002 A2). Reusa el `.visor` —el
   velo, el centrado y el botón de cerrar son los mismos— y solo cambia lo
   que un texto necesita y una imagen no: fondo, ancho de lectura y
   desplazamiento propio cuando no cabe. */
.visor-bases__cuerpo {
  max-width: min(720px, 92vw); max-height: 86vh; overflow-y: auto;
  padding: 28px 30px;
  background: var(--color-blanco);
  border-radius: var(--radio); box-shadow: var(--sombra-lg);
}
.visor-bases__cuerpo h3 { margin-top: 22px; }
.visor-bases__cuerpo h3:first-of-type { margin-top: 0; }
.visor-bases__cuerpo .info-grid,
.visor-bases__cuerpo .bases-cols { grid-template-columns: 1fr; }
.visor-bases__cuerpo .info-hero { margin: -28px -30px 22px; border-radius: var(--radio) var(--radio) 0 0; padding: 26px 30px; }
.visor-bases__cuerpo .info-hero h1 { font-size: 23px; }
.visor-bases__cuerpo .cta-bar { margin-bottom: 0; }
.visor__img {
  display: block; max-width: min(1100px, 92vw); max-height: 86vh;
  border-radius: var(--radio); box-shadow: var(--sombra-lg);
  background: var(--color-blanco);
}

/* El PDF, con el visor del propio navegador dentro.
   Medidas **fijas** y no `max-`: un `<object>` sin altura colapsa a 150
   px y el visor sale como una rendija. Con una altura de verdad, el
   scroll vertical de un documento largo lo lleva el visor por dentro,
   que además trae su zoom y su buscador. */
.visor__doc {
  display: block; width: min(900px, 92vw); height: 86vh;
  border-radius: var(--radio); box-shadow: var(--sombra-lg);
  background: var(--color-blanco); border: none;
}
/* El atributo `hidden` tiene que ganarle al `display: block` de las dos
   reglas de arriba, que si no lo pisan y se ven las dos a la vez. */
.visor__img[hidden], .visor__doc[hidden] { display: none; }

/* Lo que enseña el `<object>` cuando el navegador no sabe pintar el PDF.
   No hace falta detectarlo desde el código: son los hijos del propio
   `<object>`, y el HTML los muestra solo en ese caso. */
.visor__respaldo {
  margin: 0; padding: 40px 32px; text-align: center;
  color: var(--gris-700); font-size: 14px; line-height: 1.6;
}
.visor__respaldo-enlace { color: var(--azul-600); font-weight: 600; }
.visor__pie {
  position: fixed; left: 0; right: 0; bottom: 18px; text-align: center;
  color: var(--color-blanco); font-size: 13px; padding: 0 24px;
  text-shadow: 0 1px 3px rgba(0,0,0,.6);
}
.visor__cerrar {
  position: fixed; top: 16px; right: 20px; background: none; border: none;
  color: var(--color-blanco); font-size: 30px; line-height: 1; cursor: pointer;
  /* Ley de Fitts: el aspa mide 30 px, y el área que responde al clic se
     estira con el padding hasta pasar de 44. */
  padding: 8px 14px;
}

/* ── La miniatura que crece hasta el centro ────────────────────
   `filey.js` pone `view-transition-name: adjunto-en-vuelo` en la
   miniatura pulsada y se lo pasa a la imagen grande dentro del cambio;
   el navegador interpola posición y tamaño solo. Aquí solo se ajusta el
   ritmo: 300 ms con salida rápida y frenado suave —la misma curva que
   recomienda el artículo—, que es lo que hace que se lea como un objeto
   moviéndose y no como una animación.

   `object-fit` distinto entre las dos —la miniatura recorta, la grande
   entra completa— hace que el navegador funda una en otra durante el
   vuelo. Es exactamente lo que se quiere: se ve cómo se descubre el
   resto de la imagen. */
::view-transition-group(adjunto-en-vuelo) {
  animation-duration: 300ms;
  animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}

/* Quien pidió no animar no ve nada cruzar la pantalla: el diálogo se
   abre y se cierra y ya. `filey.js` además ni siquiera arranca la
   transición, así que esto es el cinturón del tirante. */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}


/* =========================================================
   EVT · el panel de propuestas del administrador (A1 y A2)
   =========================================================
   Portado de `prototipo/EVT/administradores/`. Lo que se añade sobre el
   prototipo son las tres cosas que allá no existían: los filtros
   avanzados, el modo de edición de los textos y la ficha de datos en
   solo lectura, que en la maqueta eran `<input readonly>` y aquí no
   tienen por qué ser campos —nadie los va a escribir—.
   =========================================================  */

/* ── A1 · La cola ──────────────────────────────────────────── */

/* Los cuatro controles del filtro se leen como **un** bloque: chips y
   buscador arriba, los dos desplegables debajo, y la cuenta al pie. Antes
   los dos desplegables iban en un `.toolbar-row` suelto —el mismo que usa
   una barra de acciones— y se leían como otro formulario que no tenía que
   ver con el de arriba. */
.filtros-panel { margin-bottom: 22px; }
.filtros-panel .filtros-cuenta { margin-bottom: 0; }

/* Alineados por la **caja** y no por la etiqueta: las dos etiquetas y el
   botón tienen alturas distintas, y alinear por arriba dejaba el botón
   flotando a media altura del primer desplegable. `--alto-etiqueta` es la
   misma medida con la que `.multi-fila` resuelve lo mismo. */
.filtros-avanzados {
  display: flex; gap: 14px; align-items: flex-start; flex-wrap: wrap;
  margin: 0 0 14px;
}
.filtros-avanzados .field { margin-bottom: 0; flex: 0 1 260px; min-width: 190px; }
.filtros-avanzados > .btn { flex: 0 0 auto; margin-top: var(--alto-etiqueta, 27px); }

@media (max-width: 620px) {
  .filtros-avanzados .field { flex: 1 1 100%; }
  .filtros-avanzados > .btn { margin-top: 0; width: 100%; }
}

/* La tabla dentro de su tarjeta. `overflow-x` es lo que la deja
   desplazarse a lo ancho en una pantalla estrecha en vez de estirar la
   página entera y sacar la barra lateral de sitio; el `border-radius` de
   `.card` la recorta sola. */
.tabla-envoltura { overflow-x: auto; }

/* El ancho mínimo es del **contenido**, no de la envoltura, y por eso es
   un modificador y no la regla de base.

   Cuando esto se escribió solo lo usaba la cola de propuestas de EVT
   —siete columnas—, así que 720px era la regla general sin coste. Al
   envolver las quince tablas del sistema deja de serlo: forzar 720px a
   una de tres columnas la manda a desplazarse en un teléfono donde
   cabía entera. Con el modificador, una tabla estrecha se encoge hasta
   donde puede y solo se desplaza si de verdad no cabe; una ancha
   —seis columnas o más, donde encogerse sería ilegible— pide su sitio.

   Se marcan `--ancha` las de seis columnas en adelante: con 32px de
   relleno por celda, por debajo de esa cuenta el mínimo intrínseco de la
   tabla ya hace el trabajo. */
.tabla-envoltura--ancha .table { min-width: 720px; }
/* Sin borde en la última fila: el marco de la tarjeta ya lo pone, y los
   dos juntos se ven como una línea doble. */
.tabla-envoltura .table tbody tr:last-child td { border-bottom: none; }

/* Una celda de dos renglones: el dato y lo que lo acompaña. Se usa igual
   en «Actividad» (título + tipo) y en «Proponente» (persona +
   institución), que es lo que permitió bajar de ocho columnas a siete sin
   quitar ningún dato. */
.celda-titulo { display: block; color: var(--tinta); font-weight: 600; line-height: 1.35; }
.celda-apoyo { display: block; margin-top: 2px; font-size: 12.5px; color: var(--gris-500); }
.celda-categoria { color: var(--gris-700); }
/* La fecha nunca parte en dos renglones: «3 sep» y «2026» separados no se
   leen como una fecha. */
.celda-fecha { white-space: nowrap; color: var(--gris-500); font-size: 13px; }
.celda-accion { text-align: right; white-space: nowrap; }
.tabla-propuestas th:last-child { text-align: right; }
/* El título de la actividad es lo que se busca al recorrer la tabla, así
   que se le da el ancho y a las columnas cortas se les quita. */
.tabla-propuestas th:nth-child(2) { width: 26%; }
.tabla-propuestas th:nth-child(3) { width: 22%; }

/* Un rótulo que solo existe para quien navega a oídas. */
.visualmente-oculto {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ── A2 · El expediente ────────────────────────────────────── */

/* El encabezado del detalle lleva tres cosas en el mismo renglón: el
   título, el estado y la vuelta a la lista. En pantalla chica el lado
   derecho baja entero y se alinea a la izquierda con el título. */
.page-head-detalle {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px 24px; flex-wrap: wrap;
}
.page-head-detalle__texto { min-width: 0; flex: 1 1 320px; }
.page-head-detalle__lado {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  /* Baja a la altura de la línea del folio, no a la del título: así el
     distintivo queda a la altura del dato que califica. */
  padding-top: 4px;
}

/* La columna izquierda es el formulario de la corrección, así que hay que
   deshacerle el margen que Django no pone pero el navegador sí. */
.expediente { margin: 0; min-width: 0; }

/* La barra que abre el modo de edición. Va **encima** de las secciones y
   no dentro del panel del dictamen —que es donde la pone el prototipo—
   porque lo que hace es sobre esta columna: un botón que desbloquea
   recuadros tiene que estar donde están los recuadros. */
.barra-edicion {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px 18px; flex-wrap: wrap; margin-bottom: 16px;
}
.barra-edicion__que { margin: 0; font-size: 13.5px; color: var(--gris-700); flex: 1 1 300px; }
.barra-edicion__que .hint { display: block; margin-top: 3px; }
.barra-edicion__acciones { display: flex; gap: 10px; flex-wrap: wrap; }

/* Un texto de la propuesta, en su recuadro. En solo lectura es una caja
   quieta —fondo gris, sin cursor de escritura— y al editar recupera el
   aspecto de un campo de verdad. La diferencia tiene que ser visible de
   un vistazo: es lo único que dice si lo que se teclee se va a guardar. */
.campo-editable textarea[readonly] {
  background: var(--gris-050); border-color: var(--gris-200);
  color: var(--gris-700); cursor: default; resize: none;
}
.campo-editable textarea[readonly]:focus {
  outline: 2px solid var(--gris-300); outline-offset: 2px; box-shadow: none;
}
.campo-editable textarea:not([readonly]) {
  background: var(--color-blanco); border-color: var(--azul-600);
  box-shadow: 0 0 0 3px var(--azul-100);
}
.campo-editable__tope { text-align: right; }

/* El dato que solo se lee, en la misma caja que el que se corrige. Es lo
   que dibuja el prototipo (`admin-evt-detalle-propuesta.html`) y lo que
   hace legible el expediente de un vistazo: rótulo arriba, valor dentro
   de su recuadro, dos por renglón. Antes eran parejas `<dt>/<dd>` sin
   caja y las tres secciones se leían como un párrafo corrido.

   Lo que **no** se hereda de `input[readonly]` es el gris apagado de un
   control bloqueado: aquí el valor es el contenido de la pantalla, no un
   campo deshabilitado, y tiene que ir con tinta de texto. Tampoco el
   `cursor: not-allowed`, que anuncia una avería donde no la hay —el
   texto se selecciona y se copia, que es justo lo que se hace con un
   correo o un teléfono—. */
.campo-lectura input[readonly],
.campo-lectura textarea[readonly] {
  background: var(--gris-050); border-color: var(--gris-200);
  color: var(--tinta); cursor: default; resize: none;
}
.campo-lectura input[readonly]:focus,
.campo-lectura textarea[readonly]:focus {
  outline: 2px solid var(--gris-300); outline-offset: 2px;
  border-color: var(--gris-200); box-shadow: none;
}
/* El correo lleva debajo su propio enlace para escribir: dentro de una
   caja de solo lectura el valor deja de ser pulsable, y avisar a quien
   propone es lo que más se hace desde esta pantalla. */
.campo-lectura .hint a { color: var(--azul-600); }

/* Parejas rótulo/valor apretadas, para el **resumen** de un dictamen ya
   emitido —resultado, categoría, quién y cuándo—: ahí no hay campos que
   corregir y la caja de `.campo-lectura` sería un recuadro por cada dato
   en una columna angosta. El expediente de la izquierda ya no la usa. */
.ficha { display: grid; grid-template-columns: 1fr 1fr; gap: 16px 18px; margin: 0 0 16px; }
.ficha:last-child { margin-bottom: 0; }
.ficha > div { min-width: 0; }
.ficha dt {
  font-weight: 600; font-size: 12.5px; color: var(--gris-500);
  text-transform: uppercase; letter-spacing: .04em; margin-bottom: 3px;
}
.ficha dd { margin: 0; font-size: 14.5px; color: var(--tinta); overflow-wrap: anywhere; }
.ficha dd .hint { display: block; }
.ficha-apilada { grid-template-columns: 1fr; gap: 12px; }

@media (max-width: 620px) { .ficha { grid-template-columns: 1fr; } }

/* Un texto largo que solo se lee —los comentarios de quien propone, el
   motivo de un rechazo ya emitido—. No es un recuadro editable, así que
   no se le da forma de campo; lo que necesita es respirar y una guarda a
   la izquierda que lo separe del resto. */
.texto-leido {
  font-size: 14px; color: var(--gris-700); line-height: 1.6;
  border-left: 3px solid var(--gris-200); padding-left: 14px;
}
.texto-leido p:last-child { margin-bottom: 0; }

/* Cada persona de la ficha, en su recuadro. Sin él, cinco autores
   seguidos se leen como una sola lista y no se ve dónde acaba uno. */
.persona-ficha {
  border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  background: var(--gris-050); padding: 14px 16px; margin-bottom: 14px;
}
.persona-ficha:last-child { margin-bottom: 0; }
.persona-ficha__head {
  display: flex; align-items: center; gap: 10px 14px; flex-wrap: wrap;
  margin-bottom: 12px;
}
.persona-ficha__rol {
  font-size: 11.5px; font-weight: 800; letter-spacing: .6px;
  text-transform: uppercase; color: var(--color-azul-institucional);
}
.persona-ficha__nombre { font-size: 15px; color: var(--tinta); }
.persona-ficha .field { margin-bottom: 0; }
/* Sobre el fondo gris de la tarjeta, la caja en solo lectura tiene que
   ser blanca para seguir leyéndose como una caja. */
.persona-ficha .campo-editable textarea[readonly] { background: var(--color-blanco); }

/* El adjunto entero es el enlace, no el nombre del archivo: se abre una
   vez por cada documento de cada propuesta, y un blanco de 14 px de alto
   dentro de un recuadro de 60 es trabajo de puntería (ley de Fitts). */
a.file-mock { color: inherit; }
a.file-mock:hover { text-decoration: none; }
a.file-mock:focus-visible { outline: 2px solid var(--color-dorado-encabezado); outline-offset: 2px; }
.file-mock__flecha { margin-left: auto; color: var(--azul-600); font-size: 15px; }

.sin-nada { margin: 0; color: var(--gris-500); font-size: 14px; }

/* ── A2 · El panel del dictamen ────────────────────────────── */

.columna-dictamen { display: flex; flex-direction: column; gap: 18px; min-width: 0; }

/* Pegado al desplazar, como en el prototipo. La decisión se toma al final
   de una lectura larga, y sin esto hay que volver arriba —o bajar hasta
   el pie— en cada propuesta.

   `--alto-topbar` + 15 px: la barra superior es fija y sin ese hueco el
   panel se le mete debajo. */
.panel-dictamen { position: sticky; top: calc(var(--alto-topbar) + 15px); }
.panel-dictamen__titulo { margin: 0 0 4px; font-size: 17px; color: var(--color-azul-institucional-enfoque); }
.panel-dictamen__sub { margin: 0 0 18px; font-size: 13.5px; color: var(--gris-500); }

/* En pantalla chica las dos columnas se apilan (`.info-grid`), y entonces
   un panel pegado tapa el expediente que hay que leer. */
@media (max-width: 920px) {
  .panel-dictamen { position: static; }
}

/* Lo ya resuelto, separado del formulario por una línea: son dos cosas
   —lo que se decidió y lo que se puede decidir— y sin la separación se
   leen como una sola. */
.resuelto { padding-bottom: 18px; margin-bottom: 18px; border-bottom: 1px solid var(--gris-200); }

.form-dictamen fieldset { border: none; margin: 0 0 16px; padding: 0; }
.form-dictamen legend {
  padding: 0; font-weight: 600; font-size: 14px; color: var(--gris-700);
  margin-bottom: 6px;
}
.form-dictamen .radio-row { gap: 18px; }

/* La autodeclaración de quien propone, delante de la casilla que la
   confirma. Son dos datos distintos —lo que dijo y lo que se comprobó— y
   el diseño tiene que dejar claro cuál es cuál: por eso lo declarado va
   en su propia línea, con su rótulo, y no dentro del texto de la casilla.
   Ver `Solicitud.es_uady` frente a `es_uady_confirmado`. */
.declaracion {
  border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  background: var(--gris-050); padding: 12px 14px;
}
.declaracion__dicho { margin: 0 0 10px; font-size: 14px; color: var(--tinta); }
.declaracion__k {
  display: block; font-size: 11.5px; font-weight: 700; letter-spacing: .5px;
  text-transform: uppercase; color: var(--gris-500); margin-bottom: 2px;
}
.declaracion .acepta { padding-top: 10px; border-top: 1px solid var(--gris-200); }
.declaracion .acepta-txt { font-weight: 600; }
.declaracion .acepta-txt .hint { display: block; margin-top: 4px; font-weight: 400; }

/* Los tres desenlaces, del mismo ancho y del mismo alto. Que ninguno sea
   más grande que otro es deliberado: los tres son decisiones del mismo
   peso, y el tamaño es lo primero que se lee como «éste es el que hay que
   pulsar». Lo que los distingue es el color. */
.acciones-dictamen { display: flex; flex-direction: column; gap: 10px; }
/* La nota de por qué los botones están apagados, debajo de ellos: el
   margen de `.note` va hacia abajo y aquí hace falta hacia arriba. */
.panel-dictamen .note { margin: 16px 0 0; }


/* ===================== Mensajes de seguimiento (CU-EVT-010) =====================
   Portado de prototipo/EVT/styles.css con los mismos nombres. Escribirle a
   un lote de propuestas: elegir a quién, redactar, revisar y enviar. No es
   el canal del dictamen, que avisa solo al aceptar, rechazar o pedir
   cambios. Tres pasos y dos pestañas más, Enviados y Plantillas.

   Aquí las pestañas y los pasos ya hechos son **enlaces**, no botones: cada
   uno es una pantalla servida, y sin JavaScript se navega igual. */

/* Pestañas: nuevo mensaje / enviados / plantillas. */
.evt-msj-tabs { display: flex; gap: 4px; border-bottom: 1.5px solid var(--gris-200); margin-bottom: 22px; flex-wrap: wrap; }
.evt-msj-tab {
  padding: 11px 16px; margin-bottom: -1.5px; font-size: 14.5px; font-weight: 600;
  color: var(--gris-500); border-bottom: 3px solid transparent; text-decoration: none;
  display: inline-flex; align-items: center; gap: 8px;
}
.evt-msj-tab:hover { color: var(--color-azul-institucional); text-decoration: none; }
.evt-msj-tab.is-active { color: var(--color-azul-institucional-enfoque); border-bottom-color: var(--color-dorado-encabezado); }
.evt-msj-tab .count { background: var(--gris-100); border-radius: 999px; padding: 0 8px; font-size: 12px; }

/* Los tres pasos. Solo se vuelve atrás pulsándolos; adelante se va con el
   botón del pie, que es el que sabe si falta algo. */
.evt-msj-pasos { display: flex; gap: 10px; list-style: none; margin: 0 0 22px; padding: 0; flex-wrap: wrap; }
.evt-msj-paso { flex: 1 1 180px; }
.evt-msj-paso > a, .evt-msj-paso > span {
  display: flex; align-items: center; gap: 11px; height: 100%;
  padding: 11px 14px; border-radius: var(--radio-sm);
  background: var(--gris-050); border: 1.5px solid var(--gris-200);
  font-size: 14px; font-weight: 600; color: var(--gris-500); text-decoration: none;
}
.evt-msj-paso .n {
  flex: 0 0 26px; height: 26px; border-radius: 50%; display: grid; place-items: center;
  background: var(--gris-200); color: var(--gris-700); font-size: 13px; font-weight: 800;
}
.evt-msj-paso small { display: block; font-size: 12px; font-weight: 400; color: var(--gris-500); }
.evt-msj-paso.is-active > span { background: var(--azul-050); border-color: var(--color-azul-institucional); color: var(--color-azul-institucional-enfoque); }
.evt-msj-paso.is-active .n { background: var(--color-azul-institucional); color: var(--color-blanco); }
.evt-msj-paso.is-hecho > a { color: var(--gris-700); }
.evt-msj-paso.is-hecho > a:hover { border-color: var(--azul-600); }
.evt-msj-paso.is-hecho .n { background: var(--ok-050); color: var(--ok-600); }

/* Tabla de destinatarios. */
.evt-msj-col-check { width: 44px; }
.evt-msj-check { width: 17px; height: 17px; margin: 2px 0 0; cursor: pointer; accent-color: var(--color-azul-institucional); }
.evt-msj-atraso { display: block; margin-top: 5px; font-size: 12px; font-weight: 700; color: var(--color-dorado-800); }
.evt-msj-reciente {
  display: inline-block; margin-top: 4px; padding: 1px 8px; border-radius: 999px;
  background: var(--warn-050); border: 1px solid var(--oro-200); color: var(--color-dorado-800);
  font-size: 11.5px; font-weight: 700;
}

/* Barra de la selección: fija abajo mientras se elige. */
.evt-msj-barra {
  position: sticky; bottom: 0; z-index: 20; margin-top: 18px;
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  padding: 14px 18px; border-radius: var(--radio);
  background: var(--color-blanco); border: 1px solid var(--gris-200); box-shadow: var(--sombra-md);
}
.evt-msj-barra .txt { flex: 1; min-width: 220px; font-size: 14px; color: var(--gris-700); }
.evt-msj-barra .txt strong { color: var(--color-azul-institucional-enfoque); font-size: 15px; }
.evt-msj-barra .evt-bloqueo { margin: 2px 0 0; }
.evt-msj-limpiar {
  background: none; border: none; padding: 0; font-family: inherit; cursor: pointer;
  font-size: 13.5px; font-weight: 600; color: var(--azul-600); position: relative;
}
.evt-msj-limpiar::after { content: ''; position: absolute; inset: -10px; }
.evt-msj-limpiar[hidden] { display: none; }
.evt-msj-nota { margin-top: 18px; }

/* Redactar: el texto a la izquierda, los campos a la derecha. */
.evt-msj-editor { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: 22px; align-items: start; }
.evt-msj-texto { min-height: 260px; resize: vertical; line-height: 1.55; }
.evt-msj-plantilla { display: flex; gap: 10px; align-items: flex-end; }
.evt-msj-plantilla .select-wrap { flex: 1; }
.evt-msj-campos { position: sticky; top: calc(var(--alto-topbar) + 16px); }
.evt-msj-campos h4 { margin: 0 0 4px; font-size: 14px; color: var(--color-azul-institucional-enfoque); }
.evt-msj-campos > p { margin: 0 0 12px; font-size: 12.5px; color: var(--gris-500); }
.evt-msj-campos-grupo { margin: 14px 0 6px; font-size: 11.5px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase; color: var(--gris-500); }
.evt-msj-campo {
  display: block; width: 100%; text-align: left; font-family: inherit;
  background: var(--color-blanco); border: 1px solid var(--gris-200); border-radius: var(--radio-sm);
  padding: 8px 11px; margin-bottom: 6px;
}
button.evt-msj-campo { cursor: pointer; }
button.evt-msj-campo:hover { border-color: var(--azul-600); background: var(--azul-050); }
.evt-msj-campo code { font-size: 13px; font-weight: 700; color: var(--color-azul-institucional); }
.evt-msj-campo small { display: block; margin-top: 1px; font-size: 12px; color: var(--gris-500); }
.evt-msj-campos .hint { display: block; margin: 10px 0 0; font-size: 12.5px; color: var(--gris-500); }

/* Cuántos correos salen con el texto de ahora. Se rehace al teclear. */
.evt-msj-cuenta {
  display: flex; gap: 10px; align-items: flex-start; margin: 4px 0 18px;
  padding: 12px 14px; border-radius: var(--radio-sm);
  background: var(--azul-050); border: 1px solid var(--azul-100);
  font-size: 13.5px; color: var(--color-azul-institucional);
}
.evt-msj-cuenta strong { font-size: 14.5px; }
.evt-msj-cuenta.is-error { background: var(--err-050); border-color: var(--err-200); color: var(--color-rojo-700); }
.evt-msj-guardar-pl { margin-right: auto; }
.evt-msj-opcion { margin-bottom: 10px; }

/* Revisar: el resumen y el correo tal como lo va a leer cada quien. */
.evt-msj-resumen { display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px; margin-bottom: 20px; }
.evt-msj-resumen .stat-card .v { font-size: 24px; }
/* Un correo no cabe a 24 px en un tercio de la fila. */
.evt-msj-resumen .stat-card .v.evt-msj-replyto { font-size: 15px; line-height: 1.3; overflow-wrap: anywhere; }
.evt-msj-lista-reciente { margin: 8px 0 0; padding: 0; list-style: none; }
.evt-msj-lista-reciente li { display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; padding: 6px 0; border-top: 1px solid var(--oro-200); }
.evt-msj-lista-reciente li:first-child { border-top: none; }
.evt-msj-lista-reciente .quien { font-weight: 700; }
.evt-msj-lista-reciente .cuando { flex: 1; }
.evt-msj-lista-reciente button {
  background: none; border: none; padding: 0; font-family: inherit; cursor: pointer;
  font-size: 13px; font-weight: 700; color: var(--err-600); position: relative;
}
.evt-msj-lista-reciente button::after { content: ''; position: absolute; inset: -10px; }

.evt-msj-nav { display: flex; align-items: center; gap: 12px; margin-bottom: 12px; flex-wrap: wrap; }
.evt-msj-nav .txt { flex: 1; font-size: 13.5px; color: var(--gris-700); }
.evt-msj-correo { border: 1px solid var(--gris-200); border-radius: var(--radio); overflow: hidden; background: var(--color-blanco); }
.evt-msj-correo__cab { margin: 0; padding: 14px 18px; background: var(--gris-050); border-bottom: 1px solid var(--gris-200); font-size: 13.5px; }
.evt-msj-correo__cab div { display: flex; gap: 10px; padding: 2px 0; }
.evt-msj-correo__cab dt { flex: 0 0 96px; color: var(--gris-500); }
.evt-msj-correo__cab dd { margin: 0; color: var(--tinta); overflow-wrap: anywhere; }
.evt-msj-correo__cab .asunto { font-weight: 700; }
.evt-msj-correo__cuerpo { padding: 20px 22px; font-size: 14.5px; line-height: 1.6; color: var(--tinta); white-space: pre-wrap; }
.evt-msj-correo__pie {
  margin: 0 22px 20px; padding-top: 14px; border-top: 1px solid var(--gris-200);
  font-size: 12.5px; line-height: 1.55; color: var(--gris-500);
}
.evt-msj-correo + .table, .note + .table { margin-top: 16px; }

/* El envío va por tandas: cada correo dice en qué va. */
.evt-msj-envio-titulo { margin: 0; font-size: 18px; color: var(--color-azul-institucional-enfoque); }
.evt-msj-progreso { height: 10px; border-radius: 999px; background: var(--gris-100); overflow: hidden; margin: 10px 0 6px; }
.evt-msj-progreso i { display: block; height: 100%; width: 0; background: var(--ok-600); transition: width .3s ease; }
.evt-msj-progreso-txt { font-size: 13px; color: var(--gris-500); margin-bottom: 16px; }
.evt-msj-estado { font-size: 13px; font-weight: 700; white-space: nowrap; }
.evt-msj-estado.is-cola { color: var(--gris-500); }
.evt-msj-estado.is-ok { color: var(--ok-600); }
.evt-msj-estado.is-err { color: var(--err-600); }
.evt-msj-estado small { display: block; font-weight: 400; color: var(--gris-500); white-space: normal; }

/* Pestaña Plantillas. */
.evt-msj-pl-cabeza { display: flex; gap: 16px; align-items: center; justify-content: space-between; flex-wrap: wrap; margin-bottom: 16px; }
.evt-msj-pl-cabeza p { margin: 0; max-width: 62ch; font-size: 13.5px; color: var(--gris-500); }
.evt-msj-acciones { white-space: nowrap; text-align: right; }
.btn.evt-msj-borrar { color: var(--err-600); }

/* ===================== Aviso del resultado (CU-EVT-009 A4) =====================
   Si el correo de aceptación o rechazo no se entregó, la propuesta queda
   sin avisar (`resultado_notificado = false`) y se reintenta desde la cola
   o desde su detalle. Reenvía el mismo correo del dictamen; no es un
   mensaje de seguimiento. */
.evt-aviso {
  padding: 13px 15px; border-radius: var(--radio-sm); font-size: 13.5px; line-height: 1.5;
  border: 1px solid var(--gris-200); background: var(--gris-050); color: var(--gris-700);
  margin-top: 14px;
}
.evt-aviso strong { display: block; font-size: 14px; margin-bottom: 2px; }
.evt-aviso small { display: block; margin-top: 4px; font-size: 12.5px; color: var(--gris-500); }
.evt-aviso .btn { margin-top: 12px; }
.evt-aviso.is-ok { background: var(--ok-050); border-color: var(--ok-200); }
.evt-aviso.is-ok strong { color: var(--ok-600); }
.evt-aviso.is-fallido { background: var(--err-050); border-color: var(--err-200); }
.evt-aviso.is-fallido strong { color: var(--color-rojo-700); }
/* La marca en la fila de la cola, debajo del estado. */
.evt-aviso-marca { display: block; margin-top: 5px; font-size: 12px; font-weight: 700; color: var(--color-rojo-700); }
.form-en-linea { display: inline; }

@media (max-width: 920px) {
  .evt-msj-editor { grid-template-columns: 1fr; }
  .evt-msj-campos { position: static; }
  .evt-msj-resumen { grid-template-columns: 1fr; }
}
