/* ==========================================================================
   DocUZ — hoja común.

   Es la base que comparten todas las vistas: la paleta, las tipografías, el
   armazón (cabecera, pie, avisos) y las piezas que se repiten de una pantalla
   a otra (botones, campos, tablas, distintivos, estados vacíos). Cada vista
   añade encima su hoja propia solo con lo que de verdad es suyo.

   El aire es el de las aplicaciones del vicerrectorado —el mismo azul, las
   mismas tipografías y el mismo armazón que Ágora—, con un gesto propio: el
   filo. Lo que en DocUZ *es* un documento —la tarjeta del acceso, las del
   repositorio— lleva en el borde de arriba un hilo de azul con el arranque en
   dorado; lo que es interfaz —barras, tablas, diálogos, controles— va liso.
   Todo sobre blanco y gris azulado, sin más color que el azul de la casa y el
   dorado en los detalles. Esa es toda la regla, y de ella cuelga el resto.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tipografías. Se sirven desde este mismo dominio y no desde un CDN: son los
   mismos ficheros que Ágora y UNIVERSA, así que las tres aplicaciones se leen
   con la misma letra aunque se abran en la misma sesión.

   Myriad Pro es la institucional y lleva todo el texto. Newsreader se queda
   para los titulares, que es donde se nota que esto es de la Universidad y no
   una herramienta cualquiera.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Myriad Pro';
  src: url("../fonts/myriad-pro-regular.6831f77fdd0e.woff2") format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Myriad Pro';
  src: url("../fonts/myriad-pro-semibold.f802386ad94b.woff2") format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Myriad Pro';
  src: url("../fonts/myriad-pro-bold.0cf926538095.woff2") format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* Newsreader es variable: un fichero por subconjunto cubre de 400 a 600. */
@font-face {
  font-family: 'Newsreader';
  src: url("../fonts/newsreader-latin.0a301b6df4c7.woff2") format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Newsreader';
  src: url("../fonts/newsreader-latin-ext.40f89707c10e.woff2") format('woff2');
  font-weight: 400 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* --------------------------------------------------------------------------
   Paleta y medidas
   -------------------------------------------------------------------------- */
:root {
  /* Azul institucional de la Universidad, el mismo que Ágora y UNIVERSA. */
  --azul: #223D71;
  --azul-oscuro: #1A2F57;
  --azul-claro: #E9EEF7;
  --acento: #B4873A;        /* dorado corporativo, para los detalles */

  --texto: #16273F;
  --texto-medio: #44546E;
  --texto-suave: #55647E;
  --texto-tenue: #5E6D88;   /* 4,8:1 sobre --fondo: el mínimo del texto pequeño */

  --fondo: #F4F5F8;
  --superficie: #FFFFFF;
  --borde: #DDE2EA;
  --borde-suave: #E7EAF0;

  --error: #8C2E33;
  --error-suave: #FDF1F1;
  --error-borde: #F0D2D3;
  --bien: #1F5C4C;
  --bien-suave: #EDF6F2;
  --bien-borde: #C9E2D8;
  --error-campo-foco: #C0787C;
  /* El aviso: el dorado de la casa sirve para un detalle, pero como letra sobre
     su fondo claro apenas se distingue; el texto va en un pardo más oscuro. */
  --aviso: #7A5716;
  --aviso-texto: #8A6520;   /* el mismo pardo, en las cifras que bajan */
  --aviso-suave: #FBF3E4;

  /* Lo que va encima del azul (el texto de un botón sólido) y los grises de en
     medio que no son ni fondo ni borde: el borde que se marca al pasar por
     encima, las filas al pasar y el relleno de un hueco. */
  --sobre-azul: #FFFFFF;
  --superficie-baja: #FAFBFD;
  --borde-fuerte: #CBD5E6;
  --relleno: #E5E9F0;

  /* Las sombras, en canales sueltos para escribirlas con su opacidad de
     siempre (`rgb(var(--sombra) / .3)`), y el anillo del foco de los campos. */
  --sombra: 26 47 87;
  --anillo: rgba(34, 61, 113, .14);
  --anillo-fuerte: rgba(34, 61, 113, .3);

  --alto-cabecera: 78px;
  --margen-lateral: 48px;

  /* Un solo alto para todo lo que se pulsa o se rellena: un botón y un campo
     con el mismo relleno no acaban midiendo lo mismo —cada uno interpreta a su
     manera la altura de línea— y en una fila de controles esos dos píxeles se
     ven como un error de montaje. */
  --alto-control: 38px;
  --radio-control: 999px;   /* nada de esquinas vivas en lo que se pulsa */
  --radio-caja: 10px;       /* cajas pequeñas que no son controles */

  /* El filo bicolor que remata las superficies de documento: un hilo de azul
     con un arranque dorado. Es el gesto propio de DocUZ y va siempre igual
     —en la tarjeta del acceso, en las del repositorio—, así que se mide aquí. */
  --filo: 3px;
  --filo-dorado: 56px;

  --serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --sans: 'Myriad Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;
  /* El monoespaciado es del sistema: aquí solo lleva lo que de verdad se copia
     y se pega —tokens, órdenes de consola, rutas—, y para eso la letra del
     terminal de quien mira es mejor que cualquiera que sirvamos nosotros. */
  --mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas,
          'Liberation Mono', monospace;
}

/* --------------------------------------------------------------------------
   Base
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  font-family: var(--sans);
  font-size: 15px;
  color: var(--texto);
  background: var(--fondo);
  -webkit-font-smoothing: antialiased;
}

/* Las vistas que se reparten la altura de la ventana —el repositorio y el
   editor, donde la lista o las hojas hacen scroll por dentro y las barras se
   quedan quietas— necesitan una altura DEFINIDA en el body: con el
   min-height general el body crece con el contenido y el flex no comprime
   nada. dvh y no vh por las barras de los navegadores móviles. */
body.pagina-alta {
  height: 100vh;
  height: 100dvh;
  min-height: 560px;
}

/* Los controles de formulario no heredan la tipografía del documento: el
   navegador les pone la suya si no se le dice lo contrario, y sin esta regla
   media interfaz se saldría de Myriad Pro. Está aquí, y no repetida en cada
   control, para que un control nuevo no pueda nacer fuera. */
button, input, select, textarea, optgroup, option { font-family: inherit; }
::placeholder { font-family: inherit; }

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

code, kbd, samp, pre { font-family: var(--mono); }

/* Un foco visible y uniforme en toda la interfaz: es la única pista de
   navegación para quien usa el teclado. */
:focus-visible {
  outline: 2px solid var(--azul);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Sobre la tinta —el pie— el anillo de tinta no se ve: ahí va en blanco. */
.pie :focus-visible { outline-color: #FFFFFF; }

/* Enlace de salto: oculto hasta que recibe el foco. */
.salto-contenido {
  position: absolute;
  left: 12px;
  top: -60px;
  z-index: 100;
  padding: 10px 18px;
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  font-size: 14px;
  font-weight: 600;
  transition: top 140ms ease;
}
.salto-contenido:focus { top: 12px; }

/* Solo para lectores de pantalla. */
.visualmente-oculto {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   El filo: el gesto propio de DocUZ.

   Un hilo de azul con el arranque en dorado, sobre el borde de arriba. Se le
   pone a lo que es un documento —la tarjeta del acceso, las del repositorio—,
   y es lo que las distingue de las superficies de interfaz, que son blancas y
   lisas. Va dentro de una caja con `overflow: hidden` para que el hilo siga la
   curva de la esquina.
   -------------------------------------------------------------------------- */
.con-filo {
  position: relative;
  /* Para que el hilo siga la curva de las esquinas de arriba. */
  overflow: hidden;
}

.con-filo::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: var(--filo);
  background: linear-gradient(
    to right,
    var(--acento) 0 var(--filo-dorado),
    var(--azul) var(--filo-dorado)
  );
}

/* --------------------------------------------------------------------------
   Marca de agua: el sello de la Universidad, como en el resto de aplicaciones
   del vicerrectorado. Fijo a la esquina de la ventana y no absoluto dentro del
   contenido, para que se quede quieto aunque la página tenga scroll. El
   contenido pasa por encima y el sello no intercepta el ratón.
   -------------------------------------------------------------------------- */
.sello {
  position: fixed;
  right: -90px;
  bottom: -120px;
  z-index: 0;
  width: 780px;
  height: auto;
  opacity: .5;
  pointer-events: none;
  user-select: none;
}

/* --------------------------------------------------------------------------
   Cabecera
   -------------------------------------------------------------------------- */
.cabecera {
  flex-shrink: 0;
  height: var(--alto-cabecera);
  padding: 0 var(--margen-lateral);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 32px;
  background: var(--superficie);
  border-bottom: 1px solid var(--borde);
  position: relative;
  z-index: 10;
}

.marca {
  display: flex;
  align-items: center;
  gap: 22px;
  color: inherit;
  min-width: 0;
}
.marca:hover { text-decoration: none; }

.marca-logo { display: block; width: 176px; height: auto; }
.marca-separador { width: 1px; height: 34px; background: var(--borde); flex-shrink: 0; }

/* La marca de DocUZ, detrás del logotipo de la Universidad: la hoja con la
   planta baja del Paraninfo, arcos y escalinata (images/docuz-marca.svg), y
   el nombre, en la misma letra gruesa que el «Universidad» del logotipo para
   que los dos se lean como una pieza.
   La imagen es decorativa; el nombre es el texto. */
.marca-docuz { display: flex; align-items: center; gap: 4px; }
.marca-icono { display: block; width: 30px; height: 30px; flex-shrink: 0; margin-left: -6px; }
.marca-nombre {
  font-family: var(--sans);
  font-size: 27px;
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1;
  color: var(--azul);
  white-space: nowrap;
  padding-top: 3px;
}

/* ---- Sesión iniciada ---- */
.sesion { display: flex; align-items: center; gap: 14px; min-width: 0; }

.sesion-usuario {
  font-size: 14.5px;
  font-weight: 600;
  color: var(--texto-medio);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Distintivo del rol: solo con administrador, que es el único que se
   distingue en algo (el resto comparte repositorio y permisos). */
.sesion-rol {
  flex-shrink: 0;
  padding: 4px 10px;
  border-radius: var(--radio-control);
  background: var(--azul-claro);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--azul);
  white-space: nowrap;
}

/* ---- Menús desplegables ----
   Un <details> con su lista flotando debajo: el de la cuenta, en la cabecera,
   y el «Más» del editor. Abre y cierra sin JavaScript; static/js/menu.js solo
   lo cierra al pulsar fuera o con Escape. */
.menu { position: relative; flex-shrink: 0; }

.menu__abre {
  list-style: none;
  cursor: pointer;
}
.menu__abre::-webkit-details-marker { display: none; }

.menu__flecha {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 140ms ease;
}
.menu[open] .menu__flecha { transform: rotate(180deg); }
/* En «Más» el único hueco era el espacio del texto: se pegaba a la «s». */
.button-ghost.menu__abre .menu__flecha { margin-left: 4px; }

.menu__lista {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 30;
  min-width: 220px;
  padding: 6px;
  display: flex;
  flex-direction: column;
  border: 1px solid var(--borde);
  border-radius: 14px;
  background: var(--superficie);
  box-shadow: 0 14px 34px rgba(18, 22, 31, .12), 0 2px 6px rgba(18, 22, 31, .06);
}

.menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--azul-oscuro);
  font: inherit;
  font-size: 13.5px;
  font-weight: 600;
  text-align: left;
  white-space: nowrap;
  cursor: pointer;
}
.menu__item:hover,
.menu__item:focus-visible { background: var(--azul-claro); color: var(--azul); text-decoration: none; }
.menu__item[disabled],
.menu__item[aria-disabled="true"] { opacity: .4; cursor: default; background: none; }

.menu__item--danger { color: var(--error); }
.menu__item--danger:hover,
.menu__item--danger:focus-visible { background: var(--error-suave); color: var(--error); }

.menu__nota {
  display: block;
  margin-top: 2px;
  font-size: 12px;
  font-weight: 500;
  color: var(--texto-tenue);
  white-space: normal;
}

.menu__rotulo {
  padding: 8px 12px 4px;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--texto-tenue);
}

.menu__separador { height: 1px; margin: 6px 4px; background: var(--borde-suave); }

/* El botón de la cuenta: la inicial en su círculo, el nombre y el rol. */
.cuenta-boton {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 0;
  height: var(--alto-control);
  padding: 0 12px 0 4px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  color: var(--azul-oscuro);
  transition: background 140ms ease;
}
.cuenta-boton:hover,
.menu[open] > .cuenta-boton { background: var(--azul-claro); }

.cuenta-boton__inicial {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--azul);
  color: #FFFFFF;
  font-family: var(--serif);
  font-size: 14px;
  font-weight: 700;
}

.menu--cuenta { min-width: 0; }

.sesion-form { margin: 0; display: flex; }

/* Selector de idioma: dos botones pegados dentro de una cápsula, como un
   interruptor de dos posiciones. Cada uno es un submit de verdad (la CSP no
   deja manejadores en línea), así que sin JavaScript funciona igual. Con la
   sesión cerrada es lo único que hay a la derecha de la marca. */
.idioma {
  margin: 0;
  display: flex;
  flex-shrink: 0;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  overflow: hidden;
}

.idioma-opcion {
  display: inline-flex;
  align-items: center;
  height: calc(var(--alto-control) - 2px);
  padding: 0 10px;
  border: 0;
  background: var(--superficie);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .08em;
  color: var(--texto-medio);
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}
.idioma-opcion + .idioma-opcion { border-left: 1px solid var(--borde); }
.idioma-opcion:hover { background: var(--azul-claro); color: var(--azul-oscuro); }

/* El activo no se puede pulsar para nada, pero sigue siendo un botón: dejarlo
   `disabled` lo sacaría del recorrido del teclado y de los lectores, que es
   justo donde hace falta que se lea cuál está puesto (aria-current). */
.idioma-opcion.es-actual {
  background: var(--azul);
  color: var(--superficie);
  cursor: default;
}
.idioma-opcion.es-actual:hover { background: var(--azul); color: var(--superficie); }

/* --------------------------------------------------------------------------
   Pie, común a los servicios del vicerrectorado: en el flujo, al final del body, y en dos líneas.

   Iba fijo abajo, y en un móvil o en un portátil bajo se quedaba con 56 px de
   pantalla en todas las vistas. Ahora es la última pieza de la columna del
   body: donde la página rueda entera sale al acabar el contenido, y en las
   que se reparten la ventana (pagina-alta, con el contenido rodando por
   dentro) se queda abajo igual que antes, pero sin tapar nada. Ahí, con poca
   altura, se junta en una sola línea.
   -------------------------------------------------------------------------- */
.pie {
  /* Sobre el sello, que es fijo y va antes, y bajo .workspace (z-index: 1),
     para que un menú abierto cerca del final siga tapando el pie. */
  position: relative;
  z-index: 0;
  flex-shrink: 0;
  margin-top: auto;
  width: 100%;
  padding: 12px 0;
  background: var(--azul);
  color: #FFFFFF;
  text-align: center;
}
.pie-contenido { max-width: 1200px; margin: 0 auto; padding: 0 20px; }
.pie p { margin: 0 0 3px; }
.pie p:last-child { margin-bottom: 0; }
.pie-principal { font-size: .8em; }
.pie-linea { font-size: .7em; }
.pie-separador { padding: 0 .5em; }
.pie a { color: #FFFFFF; text-decoration: underline; }
.pie a:hover { color: #FFFFFF; text-decoration: none; }

@media (max-height: 860px) {
  .pagina-alta .pie { padding: 7px 0; }
  .pagina-alta .pie p { display: inline; margin: 0; }
  .pagina-alta .pie-linea::before { content: "·"; padding: 0 .6em; }
}

/* --------------------------------------------------------------------------
   Avisos (mensajes de Django).

   Flotan bajo la cabecera en vez de ocupar sitio en el flujo: dentro del
   contenido, al aparecer empujaban la página entera hacia abajo, y en el
   editor —que reparte la altura de la ventana entre la barra y las hojas—
   cada aviso le robaba alto a las hojas.
   -------------------------------------------------------------------------- */
.avisos {
  position: fixed;
  top: calc(var(--alto-cabecera) + 16px);
  right: var(--margen-lateral);
  z-index: 1200;
  display: flex;
  flex-direction: column;
  gap: 10px;
  width: min(400px, calc(100vw - 2 * var(--margen-lateral)));
  margin: 0;
  padding: 0;
  list-style: none;
  /* La columna ocupa una franja alta de la ventana aunque los avisos sean
     bajitos: sin esto se quedaría con los clics de lo que hay debajo. Cada
     aviso vuelve a admitirlos. */
  pointer-events: none;
}

/* Con un diálogo modal abierto, la pila pasa a la capa de arriba como popover
   (usuarios.js, editor.js): si no, quedaba detrás del modal y de su velo. Se
   le quita lo que el navegador le pone a un popover; arriba y a la derecha
   siguen siendo los de aquí. */
.avisos[popover] {
  bottom: auto;
  left: auto;
  border: 0;
  background: none;
  color: inherit;
  overflow: visible;
}

/* Cada aviso es una tarjeta blanca de interfaz —lisa, sin filo— y el nivel lo
   dice el color del icono y el de la barra del reloj, no el fondo entero: una
   pila de tres avisos de colores distintos se leía como un semáforo. Los tonos
   van en variables para que cada nivel cambie solo eso. */
.aviso {
  --aviso-tono: var(--azul);
  --aviso-suave: var(--azul-claro);
  --aviso-barra: var(--azul);

  position: relative;
  overflow: hidden;
  pointer-events: auto;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  margin: 0;
  padding: 13px 12px 15px 14px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.45;
  background: var(--superficie);
  color: var(--texto);
  border: 1px solid var(--borde);
  box-shadow:
    0 1px 2px rgb(var(--sombra) / .06),
    0 18px 40px -18px rgb(var(--sombra) / .38);
  animation: aviso-entra 220ms cubic-bezier(.2, .8, .3, 1) both;
}

.aviso--success { --aviso-tono: var(--bien);  --aviso-suave: var(--bien-suave);  --aviso-barra: var(--bien); }
.aviso--error   { --aviso-tono: var(--error); --aviso-suave: var(--error-suave); --aviso-barra: var(--error); }
/* El dorado de la casa para la barra, que es un detalle; el icono va en un
   pardo más oscuro, que el dorado sobre su fondo claro apenas se distingue. */
.aviso--warning { --aviso-tono: var(--aviso); --aviso-suave: #FBF3E4; --aviso-barra: var(--acento); }

/* El icono lo pone static/js/avisos.js: un círculo del tono suave del nivel con
   el trazo encima. Alto fijo y alineado con la primera línea del texto, que
   es la que lo explica. */
.aviso-icono {
  flex-shrink: 0;
  display: grid;
  place-content: center;
  width: 26px;
  height: 26px;
  margin-top: -2px;
  border-radius: 50%;
  background: var(--aviso-suave);
  color: var(--aviso-tono);
}
.aviso-icono svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.aviso-texto { flex: 1; min-width: 0; padding-top: 1px; overflow-wrap: anywhere; white-space: pre-line; }

/* El botón de cerrar lo pone static/js/avisos.js: sin JavaScript sería un
   control muerto, y el aviso se retira solo de todos modos. */
.aviso-cerrar {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  margin-top: -2px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--texto-suave);
  font-size: 18px;
  line-height: 1;
  cursor: pointer;
  transition: color 140ms ease, background 140ms ease;
}
.aviso-cerrar:hover { color: var(--azul-oscuro); background: var(--azul-claro); }
.aviso-cerrar:focus-visible { outline: 2px solid var(--azul); outline-offset: 1px; }

/* El reloj: una barra al pie que se vacía de izquierda a derecha en lo que el
   aviso tarda en irse. La duración la pone avisos.js en --aviso-espera (la
   misma que su temporizador) y la clase «en-pausa» la congela mientras el
   temporizador está parado, así que barra y reloj van siempre a la par: al
   soltarla sigue desde donde se quedó. Se encoge con transform y no con el
   ancho, que no obliga a recolocar nada en cada fotograma. */
.aviso-reloj {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 3px;
  background: var(--aviso-barra);
  opacity: .7;
  transform-origin: left center;
  animation: aviso-reloj var(--aviso-espera, 6000ms) linear forwards;
}
.aviso.en-pausa .aviso-reloj { animation-play-state: paused; }
/* Al retirarse no hace falta que siga corriendo. */
.aviso.se-va .aviso-reloj { animation-play-state: paused; }

/* Al retirarse, el aviso se va por donde vino y deja de contar para la pila:
   los que queden debajo suben sin salto. */
.aviso.se-va {
  animation: aviso-sale 180ms ease forwards;
  pointer-events: none;
}

@keyframes aviso-entra {
  from { opacity: 0; transform: translateY(-10px) scale(.98); }
  to   { opacity: 1; transform: none; }
}
@keyframes aviso-sale {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-10px); }
}
@keyframes aviso-reloj {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}
/* Sin movimiento, la barra no se encoge: se va apagando en el sitio, que cuenta
   lo mismo —«esto se acaba»— sin nada que se desplace. */
@keyframes aviso-reloj-quieto {
  from { opacity: .7; }
  to   { opacity: .15; }
}

/* --------------------------------------------------------------------------
   Armazón de las vistas de trabajo

   Todas se montan igual: una barra fina de vuelta y acciones, el titular de la
   pantalla debajo y el contenido, que es lo único que hace scroll.
   -------------------------------------------------------------------------- */
.workspace {
  position: relative;
  z-index: 1;
  flex: 1;
  min-height: 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--margen-lateral);
}

/* El editor compone hojas A4 y no cabe en 1200 px: se le deja toda la ventana
   y solo conserva el margen lateral. */
.workspace--ancho { max-width: none; }

.topbar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  /* Si las acciones no caben al lado de la vuelta, bajan a su propia fila en
     vez de salirse por la derecha: en el editor, con siete botones, eso pasa
     ya por debajo de los 1340 px. */
  flex-wrap: wrap;
  gap: 10px 16px;
  padding: 18px 0;
  min-height: 74px;
}

/* La vuelta lleva el nombre de un proyecto o de un manual, que puede ser
   larguísimo: se corta con puntos suspensivos antes que empujar la barra. Va
   en bloque y no en flex porque los puntos solo salen en una caja de texto. */
.backlink {
  display: inline-block;
  min-width: 0;
  max-width: 100%;
  height: var(--alto-control);
  line-height: calc(var(--alto-control) - 2px);
  padding: 0 16px 0 13px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--azul-oscuro);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 140ms ease;
}
.backlink:hover { background: var(--azul-claro); text-decoration: none; color: var(--azul); }

.topbar__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
  margin-left: auto;
}

/* ---- Titular de la pantalla ---- */
.pagehead {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 24px;
  padding-bottom: 22px;
}

.pagehead__title { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

/* El antetítulo: la regla dorada corta y el rótulo en versalitas, el mismo
   gesto con el que se presentan las pantallas en Ágora. */
.pagehead__eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--azul);
}
.pagehead__eyebrow::before {
  content: "";
  width: 34px;
  height: 2px;
  flex-shrink: 0;
  background: var(--acento);
}

.pagehead__heading {
  margin: 0;
  font-family: var(--serif);
  font-size: 40px;
  font-weight: 400;
  line-height: 1.05;
  letter-spacing: -.01em;
  color: var(--azul-oscuro);
  word-break: break-word;
}

.pagehead__sub {
  margin: 0;
  font-size: 14px;
  color: var(--texto-suave);
  word-break: break-all;
}

.pagehead__tools { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

.pagehead__count {
  font-size: 13px;
  font-weight: 600;
  color: var(--texto-tenue);
  white-space: nowrap;
}

/* ---- El contenido, que es lo único que hace scroll ---- */
.content {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  /* Las tarjetas —proyectos, carpetas, manuales— se levantan al pasar por
     encima y su sombra se derrama fuera de la caja. Como esto es lo único que
     hace scroll, lo que asome se recorta: sin holgura, el gesto se cortaba en
     seco por arriba y por los costados. Se le da el respiro por dentro y se le
     quita por fuera, así que la retícula no se mueve ni un pixel. Por abajo
     no: debajo está el pie, y la caja que rueda se le montaría encima. */
  margin: -10px calc(var(--margen-lateral) / -2) 0;
  padding: 10px calc(var(--margen-lateral) / 2) 40px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

/* Las pantallas de una tabla por páginas (static/js/filas.js) enseñan las
   filas que caben hasta el pie: debajo de la paginación no hay nada más, y
   el respiro de siempre era sitio para una fila. */
.content:has([data-filas]) { padding-bottom: 20px; }

/* ---- Pantallas donde rueda la página entera ----
   Las listas largas (los proyectos) no se reparten la ventana: el titular se
   va con el scroll y arriba asoma la barra compacta. Sin barra vacía encima,
   el titular arranca con su propio aire. */
.workspace--rueda .content { overflow: visible; }
.pagehead--suelto { padding-top: 44px; }

/* La barra compacta: fina, fija arriba y escondida por encima del borde hasta
   que el titular sale de la vista. Su interior va en el mismo eje que la
   rejilla de debajo (1200 px y el margen lateral). */
.barra-compacta {
  position: fixed;
  inset: 0 0 auto;
  z-index: 900;
  background: rgba(255, 255, 255, .88);
  -webkit-backdrop-filter: saturate(1.4) blur(14px);
  backdrop-filter: saturate(1.4) blur(14px);
  border-bottom: 1px solid var(--borde);
  box-shadow: 0 10px 30px -24px rgba(18, 22, 31, .55);
  transform: translateY(-105%);
  transition: transform 220ms cubic-bezier(.2, .7, .2, 1);
}
.barra-compacta.es-visible { transform: none; }

.barra-compacta__dentro {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 var(--margen-lateral);
  height: 60px;
  display: flex;
  align-items: center;
  gap: 14px;
}

.barra-compacta__titulo {
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--serif);
  font-size: 21px;
  font-weight: 500;
  letter-spacing: -.02em;
  color: var(--azul-oscuro);
  white-space: nowrap;
}
/* La misma regla dorada que el antetítulo: es el titular, en pequeño. */
.barra-compacta__titulo::before {
  content: "";
  width: 22px;
  height: 2px;
  background: var(--acento);
}

.barra-compacta__n {
  font-size: 13px;
  font-weight: 600;
  color: var(--texto-tenue);
  white-space: nowrap;
}

.barra-compacta__nombre { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

.barra-compacta__accion { flex-shrink: 0; margin-left: auto; }
/* Con dos acciones, el hueco va delante de la primera y las dos juntas. */
.barra-compacta__accion + .barra-compacta__accion { margin-left: 0; }

.lead {
  margin: 0;
  max-width: 68ch;
  font-size: 15px;
  line-height: 1.6;
  color: var(--texto-suave);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Controles
   -------------------------------------------------------------------------- */
.button-solid {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--alto-control);
  padding: 0 20px;
  border: 1px solid var(--azul);
  border-radius: var(--radio-control);
  background: var(--azul);
  color: #FFFFFF;
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.button-solid:hover {
  background: var(--azul-oscuro);
  border-color: var(--azul-oscuro);
  color: #FFFFFF;
  text-decoration: none;
}

.button-solid--danger { background: var(--error); border-color: var(--error); }
.button-solid--danger:hover { background: #74262A; border-color: #74262A; }

.button-ghost {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: var(--alto-control);
  padding: 0 16px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  color: var(--azul-oscuro);
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
.button-ghost:hover {
  background: var(--azul-claro);
  border-color: var(--azul-claro);
  color: var(--azul);
  text-decoration: none;
}

/* Cuando lleva un icono y nada más: sin texto que separar, sobra el aire de
   los lados (el nombre va en `title` y `aria-label`). */
.button-ghost--icon { padding: 0 13px; }
.button-ghost--icon .button-icono { margin-right: 0; opacity: 1; }

.button-ghost--danger { color: var(--error); }
.button-ghost--danger:hover {
  background: var(--error-suave);
  border-color: var(--error-borde);
  color: var(--error);
}

.button-ghost[disabled],
.button-ghost[aria-disabled="true"] {
  opacity: .4;
  cursor: default;
}
.button-ghost[disabled]:hover,
.button-ghost[aria-disabled="true"]:hover {
  background: var(--superficie);
  border-color: var(--borde);
  color: var(--azul-oscuro);
}
/* El botón de solo icono apagado se apaga en el icono: el marco se queda. */
.button-ghost--icon[disabled],
.button-ghost--icon[aria-disabled="true"] { opacity: 1; }
.button-ghost.button-ghost--icon[disabled] > svg,
.button-ghost.button-ghost--icon[aria-disabled="true"] > svg { opacity: .35; }

/* Los iconos de los botones de la barra: a la izquierda del rótulo, un punto
   más apagados que él hasta que se pasa por encima. */
.button-icono {
  width: 15px;
  height: 15px;
  margin-right: 8px;
  flex-shrink: 0;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .7;
}
.button-ghost:hover .button-icono { opacity: 1; }

/* Los formularios que solo envuelven a un botón (borrar, revocar, cambiar el
   rol) no tienen que empujar nada: son el botón. */
.form-boton { margin: 0; display: inline-flex; }

/* --------------------------------------------------------------------------
   Campos
   -------------------------------------------------------------------------- */
.fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 18px 24px;
}

.field { display: flex; flex-direction: column; gap: 7px; min-width: 0; }

.field__label {
  font-size: 13px;
  font-weight: 600;
  color: var(--texto-medio);
}

.field__input {
  width: 100%;
  height: var(--alto-control);
  padding: 0 16px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  font-size: 15px;
  color: var(--texto);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.field__input:focus {
  outline: none;
  border-color: var(--azul);
  box-shadow: 0 0 0 3px rgba(34, 61, 113, .14);
}
.field__input::placeholder { color: var(--texto-tenue); }
/* El mismo campo, de varias líneas: una por renglón (rutas, orígenes) en
   monoespaciada, que es como se leen y se comparan. */
.field__input--area {
  height: auto;
  min-height: calc(var(--alto-control) * 1.6);
  padding: 9px 14px;
  /* Una píldora de varias líneas se come las esquinas del texto. */
  border-radius: var(--radio-caja);
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 13px;
  line-height: 1.5;
  resize: vertical;
}

.field.is-invalid .field__input,
.field__input[aria-invalid="true"] { border-color: #D8A3A6; }
.field.is-invalid .field__input:focus {
  border-color: #C0787C;
  box-shadow: 0 0 0 3px rgba(140, 46, 51, .14);
}

.field__hint { font-size: 12.5px; line-height: 1.45; color: var(--texto-tenue); }
.field__error { font-size: 12.5px; line-height: 1.45; color: var(--error); }

/* Lo contrario, más discreto: lo que sale bien. Hoy solo lo usa la contraseña
   repetida cuando ya coincide con la primera (static/js/repetir.js). */
.field.is-valid .field__input { border-color: #9CC7B6; }
.field.is-valid .field__input:focus {
  border-color: #5E9C86;
  box-shadow: 0 0 0 3px rgba(31, 92, 76, .14);
}
.field__ok { font-size: 12.5px; line-height: 1.45; color: var(--bien); }

/* El veredicto no empuja nada: va fuera del flujo, colgado bajo la repetida
   y metido en la separación que hay hasta lo siguiente del formulario. Si
   entrara en el flujo, la caja crecería un renglón al aparecer y todo lo de
   debajo saltaría a cada tecla. El hueco está siempre, vacío o no, para que
   el lector de pantalla lo anuncie al cambiar. */
.field--repetir { position: relative; padding-bottom: 10px; }
.field__repetir {
  position: absolute;
  top: calc(100% - 8px);
  left: 0;
  max-width: 100%;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}

/* ---- Desplegable de la casa ----
   El <select> del navegador no se deja vestir por dentro: ni la lista, ni la
   marca de lo elegido, ni la flecha —que cada navegador dibuja a su manera y
   pega casi al borde—. Así que el nativo se queda en el formulario, oculto,
   porque es quien lleva el valor y quien responde si no hay JavaScript, y
   delante va esto: un botón con lo elegido y la lista escrita por nosotros.
   Lo monta picker.js (base.html lo carga en todas las páginas); sin él no
   aparece y se ve el nativo de siempre. */

.picker { position: relative; }

/* Oculto pero PRESENTE: un control con display:none sigue enviando su valor. */
.picker__nativo { display: none; }

.picker__boton {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  height: var(--alto-control);
  padding: 0 15px 0 16px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  font-family: inherit;
  font-size: 15px;
  color: var(--texto);
  text-align: left;
  cursor: pointer;
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.picker__boton:hover { border-color: #C3D0E4; }
.picker__boton:focus-visible,
.picker.is-abierto .picker__boton {
  outline: none;
  border-color: var(--azul);
  box-shadow: 0 0 0 3px rgba(34, 61, 113, .14);
}
.picker__boton:disabled { cursor: default; }

.picker__valor {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La flecha, a la misma distancia del borde que el texto del otro lado, y
   girada mientras la lista está abierta. */
.picker__flecha {
  flex-shrink: 0;
  width: 13px;
  height: 13px;
  fill: none;
  stroke: var(--texto-tenue);
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 140ms ease, stroke 140ms ease;
}
.picker.is-abierto .picker__flecha { transform: rotate(180deg); stroke: var(--azul); }

.picker__lista {
  position: absolute;
  z-index: 5;
  left: 0;
  right: 0;
  top: calc(100% + 6px);
  max-height: 252px;
  margin: 0;
  padding: 5px;
  overflow-y: auto;
  list-style: none;
  border: 1px solid var(--borde);
  border-radius: var(--radio-caja);
  background: var(--superficie);
  box-shadow: 0 24px 48px -22px rgba(26, 47, 87, .5);
}
.picker__lista[hidden] { display: none; }
.picker__opcion[hidden] { display: none; }

/* Cuando debajo no queda sitio —el diálogo cae bajo en la ventana—, la lista
   se despliega hacia arriba en lugar de salirse por el pie. */
.picker__lista--arriba { top: auto; bottom: calc(100% + 6px); }

/* Y sale del diálogo de verdad: como popover, la lista vive en la capa de
   arriba del navegador, por encima del modal y sin que la recorte nadie por
   el camino —el <dialog> recorta, que la hoja de fábrica le pone
   overflow:auto y la lista se cortaba en seco en su borde—. Aquí solo se
   desmonta el sitio que le da el navegador por defecto: dónde va exactamente
   lo calcula el JS a partir del botón. */
.picker__lista:popover-open {
  position: fixed;
  inset: auto;
  margin: 0;
}

.picker__opcion {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 8px 10px;
  border-radius: 7px;
  font-size: 14.5px;
  line-height: 1.35;
  color: var(--texto);
  cursor: pointer;
}

/* Una sola señal para el ratón y para el teclado: la opción «en el punto de
   mira» se marca igual venga de donde venga, así que no hay dos resaltes
   compitiendo cuando se mezclan los dos. */
.picker__opcion.es-tanteo { background: var(--azul-claro); color: var(--azul-oscuro); }
.picker__opcion[aria-selected="true"] { font-weight: 600; color: var(--azul-oscuro); }

/* El palito de la elegida. Ocupa su sitio siempre, aunque esté apagado: si
   apareciera y desapareciera, los nombres bailarían al cambiar de opción. */
.picker__marca {
  flex-shrink: 0;
  width: 13px;
  height: 13px;
  fill: none;
  stroke: var(--azul);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: 0;
}
.picker__opcion[aria-selected="true"] .picker__marca { opacity: 1; }

.picker__texto { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* El aviso de todo el formulario (credenciales que no casan, contraseñas que
   no coinciden): la misma caja que un aviso de Django, pero anclada donde se
   ha fallado y no flotando en la esquina. */
.form__alert {
  margin: 0;
  padding: 11px 16px;
  border: 1px solid var(--error-borde);
  border-radius: var(--radio-caja);
  background: var(--error-suave);
  color: var(--error);
  font-size: 13.5px;
  line-height: 1.45;
}
.form__alert p { margin: 0; }

.check { display: flex; align-items: flex-start; gap: 10px; cursor: pointer; }

.check input[type="checkbox"] {
  margin: 2px 0 0;
  width: 16px;
  height: 16px;
  accent-color: var(--azul);
  cursor: pointer;
  flex-shrink: 0;
}

.check__text { display: flex; flex-direction: column; gap: 3px; }
.check__label { font-size: 14.5px; font-weight: 600; color: var(--texto-medio); }
.check__hint { font-size: 12.5px; line-height: 1.45; color: var(--texto-tenue); }

/* --------------------------------------------------------------------------
   Tablas

   Una tarjeta blanca con las filas dentro, no una retícula suelta sobre el
   fondo: así el listado es un objeto y no una lista de rayas, que es lo que
   permite que las tarjetas de documento, con su filo, se lean como otra cosa
   distinta.
   -------------------------------------------------------------------------- */
.table {
  display: flex;
  flex-direction: column;
  background: var(--superficie);
  border: 1px solid var(--borde);
  border-radius: var(--radio-caja);
  box-shadow: 0 18px 44px -32px rgba(26, 47, 87, .3);
  overflow: hidden;
}

.table__head,
.table__row {
  display: grid;
  gap: 20px;
  align-items: center;
  padding: 0 22px;
}

.table__head {
  padding-top: 13px;
  padding-bottom: 13px;
  border-bottom: 1px solid var(--borde-suave);
  background: #FAFBFD;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--texto-tenue);
}

.table__row {
  min-height: 60px;
  padding-top: 12px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--borde-suave);
  color: inherit;
  transition: background 140ms ease;
}
.table__row:last-child { border-bottom: 0; }

a.table__row:hover { background: var(--azul-claro); text-decoration: none; color: inherit; }

.table__row.is-inactive,
.table__row.is-revoked { opacity: .55; }

.table__cell--end { justify-self: end; text-align: right; }

/* La celda que solo está para el lector de pantalla (la tabla con roles de
   ARIA pide que todo vaya en una): sin caja, lo de dentro es la pieza de la
   rejilla, como si no estuviera. */
.table__celda { display: contents; }

/* Los rótulos que ordenan: cada uno con su flecha doble. En la lista de
   cuentas es un botón que pone static/js/usuarios.js (ordena en el
   navegador); en el selector de proyectos, un enlace (ordena el servidor).
   Sin orden, las dos puntas en gris; ordenada, solo la del sentido y en
   tinta. Hereda la letra del rótulo (versalitas, espaciado) para que la
   cabecera se lea igual que sin él. */

.rotulo-orden {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* El margen negativo arrima el texto a su columna; el ancho se lo devuelve,
     o en la columna de la derecha, que mide lo que su contenido, se cortaba. */
  max-width: calc(100% + 14px);
  margin: -4px -7px;
  padding: 4px 7px;
  border: 0;
  border-radius: 6px;
  background: transparent;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
  transition: background 140ms ease, color 140ms ease;
}
.rotulo-orden:hover { background: var(--azul-claro); color: var(--azul); text-decoration: none; }

.rotulo-orden__texto {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.rotulo-orden__flecha {
  flex-shrink: 0;
  width: 7px;
  height: 12px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}
.rotulo-orden__sube,
.rotulo-orden__baja { opacity: .35; transition: opacity 140ms ease; }

[aria-sort="ascending"] > .rotulo-orden,
[aria-sort="descending"] > .rotulo-orden { color: var(--azul-oscuro); }
[aria-sort="ascending"] .rotulo-orden__sube,
[aria-sort="descending"] .rotulo-orden__baja { opacity: 1; }
[aria-sort="ascending"] .rotulo-orden__baja,
[aria-sort="descending"] .rotulo-orden__sube { opacity: 0; }

.table__name { display: flex; align-items: center; min-width: 0; }

.table__name-text,
.table__author,
.table__label,
.table__user {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.table__name-text { font-size: 15.5px; font-weight: 600; color: var(--azul-oscuro); }
.table__label { font-size: 14.5px; }
.table__user { font-size: 14.5px; font-weight: 600; color: var(--azul-oscuro); }
.table__author { font-size: 14px; color: var(--texto-suave); }
.table__type,
.table__role { font-size: 13.5px; color: var(--texto-suave); }
.table__date {
  font-size: 13.5px;
  font-variant-numeric: tabular-nums;
  color: var(--texto-tenue);
}
.table__prefix {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--azul-oscuro);
}

/* --------------------------------------------------------------------------
   Distintivos de estado
   -------------------------------------------------------------------------- */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 4px 11px;
  border: 1px solid transparent;
  border-radius: var(--radio-control);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  white-space: nowrap;
}

/* Los tres estados de un documento, contados con la misma forma y solo con el
   peso del relleno: recién llegado de la ingesta, el filo dorado sobre blanco;
   en marcha, el gris que no compite; cerrado, el azul sólido. */
.badge--generado { background: var(--superficie); color: #8A6520; border-color: var(--acento); }
.badge--borrador { background: transparent; color: var(--texto-tenue); border-color: var(--borde); }
.badge--revisado { background: var(--azul); color: #FFFFFF; border-color: var(--azul); }

/* Revisado, pero editado después: el punto ámbar avisa de que lo revisado ya
   no es exactamente lo que hay (quién y cuándo, en el título). */
.badge--con-cambios::after {
  content: "";
  width: 6px;
  height: 6px;
  margin-left: 7px;
  border-radius: 50%;
  background: var(--acento);
}

.badge--activo { background: var(--bien-suave); color: var(--bien); border-color: var(--bien-borde); }
.badge--admin  { background: var(--azul-claro); color: var(--azul); border-color: #D3DEEE; }

/* El manual es de otra persona: aquí solo se lee. Discreto a propósito —dice
   por qué no hay botones, no reclama atención—. */
.badge--lectura { background: transparent; color: var(--texto-tenue); border-color: var(--borde); }

/* --------------------------------------------------------------------------
   Estados vacíos y sin resultados
   -------------------------------------------------------------------------- */
.noresults {
  min-height: 260px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  padding: 56px 24px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-caja);
  background: var(--superficie);
  box-shadow: 0 18px 44px -32px rgba(26, 47, 87, .3);
  text-align: center;
}

/* Con ilustración, el dibujo a un lado y el texto al otro. */
.noresults__texto {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  min-width: 0;
}
.noresults__texto > :is(.button-ghost, .button-solid) { margin-top: 4px; }
@media (min-width: 720px) {
  .noresults:has(> .noresults__texto) {
    flex-direction: row;
    gap: 40px;
    padding: 28px 48px;
    text-align: left;
  }
  .noresults__texto { align-items: flex-start; }
}

.vacio-ilustracion {
  display: block;
  flex: 0 0 auto;
  width: 176px;
  max-width: 100%;
  height: auto;
  user-select: none;
  -webkit-user-drag: none;
}
.noresults > .vacio-ilustracion { width: 160px; }

.noresults__eyebrow {
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--texto-tenue);
}

.noresults__title {
  margin: 0;
  font-family: var(--serif);
  font-size: 27px;
  font-weight: 400;
  letter-spacing: -.01em;
  color: var(--azul-oscuro);
}

.noresults__note {
  margin: 0;
  max-width: 44ch;
  font-size: 14.5px;
  line-height: 1.55;
  color: var(--texto-suave);
  text-wrap: pretty;
}

/* --------------------------------------------------------------------------
   Buscar, ordenar, cambiar de vista y pasar de página: el repositorio y el
   selector de proyectos los llevan iguales.
   -------------------------------------------------------------------------- */

/* --- La barra de la lista -------------------------------------------------------
   Por conceptos: junto al titular, solo lo que crea; buscar, ordenar y la
   vista van en su propia barra, pegada a la lista a la que afectan (más
   cerca de ella que de lo de arriba). La búsqueda y el orden a la izquierda,
   que se leen juntos; la vista, al otro lado. */

.barra-lista {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-bottom: -10px;
}
.barra-lista .search { flex: 0 1 360px; width: auto; }
.barra-lista .viewswitch { margin-left: auto; }

/* Lo que cambia la barra: la lista y su paginación, con el aire del resto. */
.listado {
  display: flex;
  flex-direction: column;
  gap: 24px;
  min-width: 0;
}

@media (max-width: 600px) {
  .barra-lista .search { flex: 1 1 100%; }
}

/* --- Búsqueda --------------------------------------------------------------- */

.search {
  display: flex;
  align-items: center;
  gap: 10px;
  width: min(320px, 42vw);
  min-width: 180px;
  height: var(--alto-control);
  padding: 0 16px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.search:focus-within {
  border-color: var(--azul);
  box-shadow: 0 0 0 3px var(--anillo);
}

.search__icon {
  flex-shrink: 0;
  width: 15px;
  height: 15px;
  fill: none;
  stroke: var(--texto-tenue);
  stroke-width: 1.6;
  stroke-linecap: round;
}
.search:focus-within .search__icon { stroke: var(--azul); }

.search__input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  outline: none;
  font-size: 14.5px;
  color: var(--texto);
}
.search__input::placeholder { color: var(--texto-tenue); }

/* El aspa que Chrome le pone a los campos de búsqueda no casa con nada de
   aquí; se quita y se limpia con la tecla de siempre. */
.search__input::-webkit-search-cancel-button { -webkit-appearance: none; }

/* --- Conmutador de vista -----------------------------------------------------
   Cápsulas con las opciones (solo iconos) dentro, del alto de los botones
   vecinos: lista y tarjetas en una; el árbol, en la suya. La puesta va en
   azul sólido: es un estado, no un botón más. */

.viewswitch {
  position: relative;
  isolation: isolate;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}

.viewswitch__capsula {
  display: flex;
  align-items: center;
  gap: 2px;
  height: var(--alto-control);
  padding: 3px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  transition: background 200ms ease, border-color 200ms ease;
}

/* Se apaga por partes y no con `opacity`, que la haría su propio contexto de
   apilado: la tinta, al entrar, le pasaría por encima a los iconos. */
.viewswitch__capsula:not(:has(.is-active, :hover, :focus-visible)) {
  border-color: color-mix(in srgb, var(--borde) 55%, transparent);
  background: color-mix(in srgb, var(--superficie) 55%, transparent);
}
.viewswitch__capsula:not(:has(.is-active, :hover, :focus-visible)) .viewswitch__icono { opacity: .55; }

/* Son botones de un formulario (elegir vista guarda preferencia, así que va
   por POST), de ahí el reseteo: un <button> no hereda ni la letra ni el color
   y trae borde propio. */
.viewswitch__option {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  aspect-ratio: 1.25;
  padding: 0;
  border: 0;
  font-family: inherit;
  cursor: pointer;
  border-radius: var(--radio-control);
  background: transparent;
  font-size: 13px;
  font-weight: 600;
  color: var(--texto-suave);
  white-space: nowrap;
  transition: background 140ms ease, color 140ms ease;
}
.viewswitch__option:hover { background: var(--azul-claro); color: var(--azul); text-decoration: none; }

.viewswitch__option.is-active,
.viewswitch__option.is-active:hover { background: var(--azul); color: var(--sobre-azul); }

/* Con JavaScript la tinta es una sola, que se desliza de una opción a otra en
   cuanto se pulsa, de una cápsula a la otra si hace falta (repositorio.js la
   coloca). Va entre las cápsulas y los iconos. Sin él, se queda el fondo de
   arriba, que también vale al imprimir o si el guion no llega. */
.viewswitch__tinta {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1;
  border-radius: var(--radio-control);
  background: var(--azul);
  pointer-events: none;
  transition: transform var(--vista-duracion) var(--transicion-curva);
}

.viewswitch--viva .viewswitch__option { position: relative; z-index: 2; }
.viewswitch--viva .viewswitch__option.is-active,
.viewswitch--viva .viewswitch__option.is-active:hover { background: transparent; }

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

.viewswitch__icono {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: opacity 200ms ease;
}

/* --- Del listado a las tarjetas -----------------------------------------------
   Al cambiar de vista (repositorio.js), cada fila se convierte en su tarjeta:
   la caja viaja de un sitio a otro cambiando de forma y, dentro, una imagen
   se funde en la otra. Las dos van a su tamaño natural, pegadas arriba a la
   izquierda y recortadas por la caja: estiradas a la forma de la otra, una
   fila de 900 × 56 metida en una tarjeta se leería como un borrón. */

::view-transition-group(.pieza) {
  /* Las capas van en el orden del «antes»: una carpeta, que estaba por encima
     de la tabla en la página, se pintaría debajo de la caja que se le abre. */
  z-index: 1;
  animation-duration: var(--vista-duracion);
  animation-timing-function: var(--transicion-curva);
}

::view-transition-image-pair(.pieza) {
  overflow: clip;
  border-radius: var(--radio-caja);
}

::view-transition-old(.pieza),
::view-transition-new(.pieza) {
  height: 100%;
  object-fit: none;
  object-position: left top;
  animation-duration: var(--vista-duracion-fundido);
}

/* --- Lo que hay alrededor -----------------------------------------------------
   Lo que no cambia de vista pero sí de sitio, porque la lista mide otra cosa:
   viaja en vez de fundirse en dos alturas a la vez, que lo dejaría doble.

   El conmutador va aparte y siempre vivo: nada de foto del «antes», y el
   «después» es la página de verdad, así que la tinta se ve deslizar aunque la
   transición empiece a mitad de camino. Una transición, además, no se queda
   con los clics de quien cambia de idea: se atraviesa. */

html:active-view-transition-type(vista)::view-transition { pointer-events: none; }

html:active-view-transition-type(vista) .barra-lista { view-transition-name: repo-barra; }
html:active-view-transition-type(vista) .viewswitch { view-transition-name: conmutador; }
html:active-view-transition-type(vista) [data-lista] > .paginas { view-transition-name: repo-paginas; }

::view-transition-old(conmutador) { display: none; }
::view-transition-new(conmutador) { animation: none; }

/* --- Al árbol y desde el árbol ------------------------------------------------
   Son más que una forma de ver lo mismo: las carpetas de arriba se convierten
   en sus filas (data-pieza) y lo que colgaba de ellas se despliega debajo, en
   cascada (repositorio.js). Para que se lea así, la caja de la tabla crece
   desde su forma de antes. No se estira: lo de dentro se queda a su tamaño y
   lo recorta el borde. */

html:active-view-transition-type(arbol) [data-lista] .table { view-transition-name: arbol-caja; }
html:active-view-transition-type(arbol) [data-lista] .table__head { view-transition-name: arbol-rotulos; }

/* Las carpetas y la barra se cruzan, porque ellas bajan a ser filas mientras
   ella sube a ocupar su sitio. La barra va opaca y por encima: lo que cruza
   pasa por detrás en vez de leerse un texto sobre otro. El relleno da aire a lo que cruza y el margen negativo lo devuelve, sin mover nada. */
html:active-view-transition-type(arbol) .barra-lista {
  padding: 8px;
  margin: -8px -8px -18px;
  background: var(--fondo);
}
::view-transition-group(repo-barra) { z-index: 2; }
::view-transition-group(conmutador) { z-index: 3; }

::view-transition-group(repo-barra),
::view-transition-group(conmutador),
::view-transition-group(repo-paginas),
::view-transition-group(arbol-caja),
::view-transition-group(arbol-rotulos) {
  animation-duration: var(--vista-duracion);
  animation-timing-function: var(--transicion-curva);
}

::view-transition-group(arbol-caja) {
  overflow: clip;
  border-radius: var(--radio-caja);
}

::view-transition-old(arbol-caja),
::view-transition-new(arbol-caja) {
  height: 100%;
  object-fit: none;
  object-position: left top;
}

/* Sin documentos que mostrar, la otra vista no cambiaría nada: apagada. */
.viewswitch__option:disabled,
.viewswitch__option:disabled:hover { background: transparent; color: var(--texto-tenue); opacity: .5; cursor: not-allowed; }

/* --- Las páginas de una lista ------------------------------------------------
   templates/partials/paginacion.html: a la izquierda cuántos hay y cuántas
   por página; a la derecha, las flechas y los números. La usan el selector
   de proyectos y el repositorio. */

.paginas {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  font-size: 13px;
  color: var(--texto-tenue);
}
/* Flechas a los lados y, entre ellas, los números: la primera y la última
   siempre, para saltar a los extremos, y puntos donde se saltan páginas. */
.paginas__botones { display: flex; align-items: center; gap: 4px; }
.paginas__boton {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 32px;
  height: 32px;
  padding: 0 8px;
  border: 1px solid transparent;
  border-radius: var(--radio-control);
  background: transparent;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--texto-medio);
  transition: background 140ms ease, border-color 140ms ease, color 140ms ease;
}
/* Enlaces si van al servidor; botones si la lista pagina en el navegador
   (static/js/usuarios.js). */
button.paginas__boton { cursor: pointer; }
:is(a, button).paginas__boton:not(.is-actual):hover { background: var(--azul-claro); color: var(--azul-oscuro); text-decoration: none; }
.paginas__boton.is-actual { background: var(--azul); color: var(--sobre-azul); }
.paginas__flecha { border-color: var(--borde); background: var(--superficie); padding: 0; }
:is(a, button).paginas__flecha:hover { border-color: var(--borde-fuerte); }
.paginas__flecha svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.paginas__flecha:is([aria-disabled="true"], :disabled) { opacity: .4; pointer-events: none; }
.paginas[hidden] { display: none; }
.paginas__salto { min-width: 20px; text-align: center; color: var(--texto-tenue); }

/* Cuántas por página: las que caben o un número fijo (static/js/filas.js). */
.paginas__cuenta { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 8px 18px; }
.paginas__filas { display: inline-flex; align-items: center; gap: 8px; }
.paginas__filas[hidden] { display: none; }
/* Cuántas por página: el desplegable de la casa (picker.js, autodoc.css), a
   la medida del pie. Lo viste static/js/filas.js; el nativo no llega a verse,
   que sin JavaScript la caja no sale. */
.picker--filas .picker__boton {
  width: auto;
  height: 30px;
  gap: 8px;
  padding: 0 10px 0 12px;
  font-size: 13px;
  font-weight: 600;
  color: var(--azul-oscuro);
}
.picker--filas .picker__flecha { width: 12px; height: 12px; }
.picker--filas.is-abierto .picker__boton { box-shadow: 0 0 0 3px var(--anillo); }
.picker--filas .picker__lista {
  min-width: 150px;
  padding: 4px;
  border-radius: 9px;
}
.picker--filas .picker__opcion { padding: 6px 9px; font-size: 13px; }
.picker--filas .picker__marca { width: 11px; height: 11px; }

/* Despegada de la tabla o de las tarjetas. */
.listado > .paginas { margin-top: 18px; }

/* --- Ordenar -------------------------------------------------------------------
   El desplegable de la casa y, pegada, la flecha del sentido. En la tabla, los
   rótulos (.rotulo-orden, más arriba) son el mismo orden por el camino corto. */
.ordenar {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 6px;
}
.ordenar .picker { width: 184px; }
/* El botón de aplicar es para cuando no hay JavaScript; con él lo esconde
   static/js/lista.js, y el display de .button-ghost ganaría al hidden. */
.ordenar [data-sin-js][hidden] { display: none; }
/* Sin JavaScript se ve el select del navegador, que al menos tenga el alto
   de sus vecinos. */
.ordenar__nativo:not(.picker__nativo) {
  height: var(--alto-control);
  padding: 0 10px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  font: inherit;
  font-size: 14px;
  color: var(--texto);
}

.ordenar__sentido {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: var(--alto-control);
  height: var(--alto-control);
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
  background: var(--superficie);
  color: var(--texto-suave);
  transition: border-color 140ms ease, color 140ms ease, background 140ms ease;
}
.ordenar__sentido:hover {
  border-color: var(--borde-fuerte);
  background: var(--azul-claro);
  color: var(--azul);
  text-decoration: none;
}
.ordenar__flecha {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  transition: transform 220ms cubic-bezier(.2, .8, .2, 1);
}
/* Una sola flecha: hacia arriba de menos a más, girada de más a menos. */
.ordenar__sentido[data-sentido="desc"] .ordenar__flecha { transform: rotate(180deg); }

/* En el móvil la búsqueda se queda la fila y, debajo, el orden se estira
   hasta la vista. */
@media (max-width: 600px) {
  .ordenar .picker { width: auto; flex: 1; }
  .ordenar { flex: 1 1 auto; }
}

/* --- Buscar, ordenar y pasar de página, sin recargar ----------------------------
   static/js/lista.js cambia lo de abajo (data-lista) dentro de una transición
   de vista del tipo «lista», como la lista de cuentas (usuarios.css): las
   piezas que se ven enteras, antes y después, se deslizan de su sitio al
   nuevo; la que se va se funde donde estaba y la que llega aparece en la
   suya. El nombre lo lleva cada pieza en una variable y solo se aplica
   mientras dura la transición, así que no estorba al conmutador de vista,
   que nombra las mismas piezas a su manera. */

/* Mientras llega la página nueva, solo si tarda. */
[data-lista] { transition: opacity 200ms ease; }
[data-lista].esta-cargando { opacity: .55; }

html:active-view-transition-type(lista) [data-lista] .viaja {
  view-transition-name: var(--lista-pieza);
  view-transition-class: lista-pieza;
}
html:active-view-transition-type(lista) [data-lista] .paginas {
  view-transition-name: lista-paginas;
}

::view-transition-group(*.lista-pieza) {
  animation-duration: var(--transicion-duracion, 280ms);
  animation-timing-function: var(--transicion-curva, cubic-bezier(.2, .8, .2, 1));
}
::view-transition-old(*.lista-pieza):only-child {
  animation-duration: var(--transicion-duracion-salida, 180ms);
  animation-timing-function: ease-out;
}
::view-transition-new(*.lista-pieza):only-child {
  animation-delay: 80ms;
  animation-fill-mode: both;
}

@media (prefers-reduced-motion: reduce) {
  .ordenar__flecha,
  [data-lista] { transition: none; }
}

/* --------------------------------------------------------------------------
   Diálogos: blancos, redondeados y con sombra.
   -------------------------------------------------------------------------- */
.dialogo {
  padding: 0;
  border: 1px solid var(--borde);
  border-radius: 12px;
  background: var(--superficie);
  color: var(--texto);
  box-shadow: 0 32px 64px -28px rgba(26, 47, 87, .45);
}
.dialogo::backdrop { background: rgba(26, 47, 87, .45); }

/* El de preguntar, que sale en cualquier página (templates/base.html): la
   anchura va aquí y no en la hoja del editor, que solo se sirve en el detalle. */
.dialogo--estrecho { width: min(430px, calc(100vw - 48px)); }

.dialogo__body { display: flex; flex-direction: column; gap: 14px; padding: 28px; }

.dialogo__title {
  margin: 0;
  font-family: var(--serif);
  font-size: 26px;
  font-weight: 400;
  letter-spacing: -.01em;
  color: var(--azul-oscuro);
}

.dialogo__note {
  margin: 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--texto-suave);
  text-wrap: pretty;
}

/* Lo que conviene saber antes de confirmar (mover un manual publicado). */
.dialogo__note--aviso {
  padding: 10px 12px;
  border: 1px solid #E9D8B4;
  border-radius: var(--radio-caja);
  background: #FBF3E4;
  color: #7A5716;
}

.dialogo__actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 12px;
  padding-top: 6px;
}

/* --------------------------------------------------------------------------
   Adaptación a pantallas menores
   -------------------------------------------------------------------------- */
@media (max-width: 900px) {
  :root { --margen-lateral: 24px; }

  .cabecera { height: auto; padding: 14px var(--margen-lateral); flex-wrap: wrap; gap: 14px; }
  .marca { gap: 14px; }
  .marca-logo { width: 132px; }
  .marca-nombre { font-size: 22px; }
  .marca-icono { width: 25px; height: 25px; }
  .sesion-usuario { display: none; }

  .avisos { top: 92px; }

  .pagehead { flex-direction: column; align-items: flex-start; }
  .pagehead__heading { font-size: 32px; }

  /* El sello no se va, se encoge: en una pantalla estrecha ocuparía media
     vista, pero sigue siendo la marca de la casa. */
  .sello { width: 520px; right: -130px; bottom: -150px; opacity: .4; }
}

@media (max-width: 640px) {
  body, body.pagina-alta { height: auto; min-height: 100vh; min-height: 100dvh; }
  .pagehead--suelto { padding-top: 28px; }
  .barra-compacta__dentro { height: 54px; gap: 10px; }
  .barra-compacta__titulo { font-size: 18px; }
  .barra-compacta__titulo::before { display: none; }
  /* En un móvil solo cabe una acción junto al nombre: se queda la última,
     crear documento. Nueva carpeta sigue a mano en la cabecera. */
  .barra-compacta__n,
  .barra-compacta__accion:not(:last-child) { display: none; }
  .barra-compacta__accion:last-child { margin-left: auto; }
  .content { overflow: visible; }
  .topbar { flex-wrap: wrap; min-height: 0; }
  .topbar__actions { margin-left: 0; flex-wrap: wrap; }
  .sello { width: 400px; right: -120px; bottom: -130px; }
}

@media (prefers-reduced-motion: reduce) {
  .aviso, .aviso.se-va { animation-duration: 1ms; }
  .aviso-reloj { transform: none; animation-name: aviso-reloj-quieto; }
  .barra-compacta { transition: none; }
  * { scroll-behavior: auto; }
}

/* --------------------------------------------------------------------------
   Con el dedo.

   38 px de control se quedan cortos para una yema: 44 es el mínimo de las
   guías de accesibilidad para un objetivo táctil. Solo cambia donde el
   puntero es grueso; con ratón la interfaz sigue como estaba. Lo que no puede
   crecer sin descolocar su sitio —la × de los avisos— agranda solo la zona
   que recibe el toque, con un ::after transparente.
   -------------------------------------------------------------------------- */

@media (pointer: coarse) {
  :root { --alto-control: 44px; }
  .aviso-cerrar { position: relative; }
  .aviso-cerrar::after { content: ""; position: absolute; inset: -11px; }
}

/* ==========================================================================
   Transiciones entre vistas (View Transitions).

   Al cambiar de página, la cabecera y el pie se quedan quietos y lo que
   cambia es el contenido, que se funde deprisa y sin moverse. Cada
   navegación lleva un tipo, que pone static/js/transiciones.js: `adelante`
   (un enlace, un formulario), `atras` (el botón de volver) o `recarga` (la
   misma dirección otra vez). Los cambios dentro de una página que pasan por
   `transicion()` no llevan ninguno de los tres.

   Grupos con nombre, para quien los necesite desde su hoja:
     cabecera   la barra de arriba (.cabecera)
     contenido  el <main id="contenido"> de cada vista
     pie        el pie (.pie)
     avisos     la columna de avisos (.avisos)
     compartido-<parte>  los elementos compartidos de data-transicion, todos
                con la clase `compartido` (y `compartido-escala` los que se
                dejan escalar): ::view-transition-group(*.compartido). Hoy
                apagados entre páginas (ver transiciones.js).

   Los tiempos van en variables para que cada vista ajuste los suyos sin
   repetir la curva. Sin transiciones entre páginas (Firefox, Safari antiguo)
   el contenido nuevo al menos se enciende al llegar (más abajo). Con
   «reducir movimiento», no hay transición entre páginas.
   ========================================================================== */
:root {
  --transicion-duracion: 280ms;        /* el cambio de contenido */
  --transicion-duracion-salida: 180ms; /* lo que se va, más deprisa */
  --transicion-duracion-morph: 360ms;  /* un elemento que se transforma en otro */
  --transicion-curva: cubic-bezier(.2, .8, .2, 1);   /* sale rápido y se posa */
  --transicion-curva-salida: cubic-bezier(.4, 0, 1, 1);
  --vista-duracion: 280ms;             /* cambiar de vista: se hace a menudo */
  --vista-duracion-fundido: 180ms;     /* dentro de eso, una foto que se funde en otra */
}

@view-transition { navigation: auto; }

@media (prefers-reduced-motion: reduce) {
  @view-transition { navigation: none; }
}

.cabecera { view-transition-name: cabecera; }
.pie { view-transition-name: pie; }
.avisos { view-transition-name: avisos; }
#contenido { view-transition-name: contenido; }

/* El tiempo y la curva de todo lo que se mueve, por defecto. Cada grupo con
   nombre propio puede pisarlo con su ::view-transition-group(nombre), que
   pesa más que el comodín. */
::view-transition-group(*),
::view-transition-old(*),
::view-transition-new(*) {
  animation-duration: var(--transicion-duracion);
  animation-timing-function: var(--transicion-curva);
}

/* La cabecera y el pie no se mueven ni se funden: cambian en seco, que entre
   una página y otra son los mismos (y si no lo son —al entrar—, el
   cambio es pequeño y fundirlo los dejaría borrosos un instante).
   El pie, además, no mide lo mismo en todas las vistas (en las que reparten
   la ventana va en una línea): fundidos, los dos pies de alto distinto
   dejaban una franja gris. Donde cambia de sitio —tras una lista larga queda
   fuera de la vista— salta al suyo, como sin transición. */
::view-transition-group(cabecera),
::view-transition-group(pie) { animation: none; }
::view-transition-old(cabecera),
::view-transition-old(pie) { display: none; }
::view-transition-new(cabecera),
::view-transition-new(pie) { animation: none; }

/* Los avisos traen su propia entrada (aviso-entra) y se pintan en vivo en la
   página nueva: los de la página de antes se van sin más. Con nombre propio
   para que no queden debajo del contenido mientras dura la transición. */
::view-transition-group(avisos) { animation: none; }
::view-transition-old(avisos) { display: none; }
::view-transition-new(avisos) { animation: none; }

/* El contenido: las dos capturas a su tamaño natural, centradas y arriba,
   sin escalarse aunque la caja cambie de anchura o de alto (el editor ocupa
   toda la ventana y una lista 1200 px). Escaladas, el texto crecía o
   menguaba en mitad del cambio. */
::view-transition-old(contenido),
::view-transition-new(contenido) {
  width: auto;
  height: auto;
  inset-inline-start: 50%;
  translate: -50% 0;
}

/* ---- El cambio de contenido al navegar ----
   Un fundido muy corto y sin moverse: el de antes se apaga en 90 ms y el
   nuevo se enciende en 150. Se probaron deslizarlo de lado, subirlo,
   acercarlo y que las tarjetas se transformaran en el titular de la página
   siguiente, y todo eso mareaba al ir de carpeta en carpeta o de una sección
   a otra: aquí el contenido solo cambia, deprisa. `recarga` y los cambios
   dentro de la página se quedan como están. */
html:active-view-transition-type(adelante, atras)::view-transition-old(contenido) {
  animation: 90ms linear both transicion-sale;
}
html:active-view-transition-type(adelante, atras)::view-transition-new(contenido) {
  animation: 150ms ease both transicion-entra;
}

@keyframes transicion-sale { to { opacity: 0; } }
@keyframes transicion-entra { from { opacity: 0; } }

/* Donde no hay transiciones entre páginas (Firefox, Safari antiguo), al
   menos la llegada: el contenido nuevo se enciende igual de deprisa. La
   salida no se imita, que habría que retener cada enlace y la navegación
   tardaría más. La clase la pone static/js/transiciones.js antes del primer
   pintado. Con `backwards` y no `both`: al acabar no queda nada aplicado
   (una opacidad animada para siempre haría del contenido un contexto de
   apilamiento). */
@media (prefers-reduced-motion: no-preference) {
  .sin-transicion-entre-paginas #contenido {
    animation: 150ms ease backwards transicion-entra;
  }
}

/* Los elementos compartidos (data-transicion): la caja viaja y cambia de
   tamaño, y el contenido de cada lado se queda a su tamaño natural,
   arrimado arriba a la izquierda y recortado por la caja, en vez de
   estirarse: una tarjeta que se hace titular no deforma las letras. Los que
   se dejan escalar (imágenes, o data-transicion-escala) llenan la caja
   recortando lo que sobre, sin deformarse tampoco. */
::view-transition-group(*.compartido) {
  animation-duration: var(--transicion-duracion-morph);
  overflow: clip;
}
::view-transition-old(*.compartido),
::view-transition-new(*.compartido) {
  animation-duration: var(--transicion-duracion-morph);
  width: 100%;
  height: 100%;
  object-fit: none;
  object-position: left top;
}
::view-transition-old(*.compartido-escala),
::view-transition-new(*.compartido-escala) {
  object-fit: cover;
  object-position: center;
}
/* El de dentro sale antes de que la caja llegue: los dos a la vez, a medio
   camino, se leían como un borrón. */
::view-transition-old(*.compartido) {
  animation-duration: calc(var(--transicion-duracion-morph) * .6);
}

/* Para quien todavía no tenga clases de transición (view-transition-class):
   al menos la parte principal se transforma igual. */
::view-transition-group(compartido-principal) {
  animation-duration: var(--transicion-duracion-morph);
}

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* ---- Diálogos y menús: entran, no aparecen ----
   Los diálogos (.dialogo) suben unos pixeles mientras se encienden, y el
   velo de detrás se funde; al cerrarse, al revés. Se mueven con `translate`
   y no con `transform` ni escala: el editor mide sus diálogos (el recorte, los
   resaltes) nada más abrirlos, y una escala le daría otro ancho. Donde no hay
   @starting-style el diálogo aparece en seco, como antes; donde no hay
   `overlay`/`allow-discrete`, se cierra en seco. */
@media (prefers-reduced-motion: no-preference) {
  .dialogo {
    transition:
      opacity 180ms var(--transicion-curva),
      translate 240ms var(--transicion-curva),
      overlay 240ms allow-discrete,
      display 240ms allow-discrete;
  }
  .dialogo:not([open]) { opacity: 0; translate: 0 8px; }
  @starting-style {
    .dialogo[open] { opacity: 0; translate: 0 12px; }
  }

  .dialogo::backdrop {
    transition:
      opacity 200ms ease,
      overlay 240ms allow-discrete,
      display 240ms allow-discrete;
  }
  .dialogo:not([open])::backdrop { opacity: 0; }
  @starting-style {
    .dialogo[open]::backdrop { opacity: 0; }
  }

  /* La lista de los menús (<details data-menu>) entra en persiana: la mueve
     static/js/menu.js. */
}
