/* ==========================================================================
   Nimbrook — 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 una herramienta de trabajo, no el de un folleto: tinta casi
   negra sobre blanco cálido y un solo color, el ámbar, reservado a lo que hay
   que mirar. Encima va el gesto propio de la casa: el filo. Lo que en Nimbrook
   *es* un documento —la tarjeta del acceso, las del repositorio— lleva en el
   borde de arriba un hilo de tinta con el arranque en ámbar; lo que es
   interfaz —barras, tablas, diálogos, controles— va liso. Esa es toda la
   regla, y de ella cuelga el resto.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Tipografías. Se sirven desde este mismo dominio y no desde un CDN: la
   aplicación se lee igual en cualquier red y no le pide nada a nadie de fuera.

   Space Grotesk es la letra de la marca y de los titulares: geométrica, con
   unos cuantos trazos raros que se reconocen de lejos. Inter lleva el texto,
   que es el trabajo largo —diez horas seguidas en el editor— y el que más
   importa que no canse.

   Las dos son variables: un fichero por subconjunto cubre todos los pesos, así
   que son cuatro peticiones en total y ninguna se repite por cambiar de peso.
   -------------------------------------------------------------------------- */
@font-face {
  font-family: 'Space Grotesk';
  src: url("../fonts/space-grotesk-latin.25ea4a783c12.woff2") format('woff2');
  font-weight: 400 700;
  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: 'Space Grotesk';
  src: url("../fonts/space-grotesk-latin-ext.dea099b7d5a5.woff2") format('woff2');
  font-weight: 400 700;
  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;
}
@font-face {
  font-family: 'Inter';
  src: url("../fonts/inter-latin.65850a373e25.woff2") format('woff2');
  font-weight: 100 900;
  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: 'Inter';
  src: url("../fonts/inter-latin-ext.01ba6c2a184b.woff2") format('woff2');
  font-weight: 100 900;
  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 {
  /* La tinta, en tres tonos: el de trabajo (enlaces, pie, distintivos), el
     fuerte (titulares) y el tenue (fondos de píldora y de código). */
  --tinta: #1B2230;
  --tinta-fuerte: #12161F;
  --tinta-tenue: #ECEDF1;
  --acento: #D89A3C;        /* ámbar: el único color, y solo en los detalles */

  --texto: #1A1E26;
  --texto-medio: #454B57;
  --texto-suave: #5A6170;
  --texto-tenue: #666D7B;   /* 4,7:1 sobre --fondo: el mínimo del texto pequeño */

  --fondo: #F5F4F1;
  --superficie: #FFFFFF;
  --borde: #E2E0DA;
  --borde-suave: #EBE9E3;

  --error: #8C2E33;
  --error-fuerte: #74262A;
  --error-suave: #FDF1F1;
  --error-borde: #F0D2D3;
  --error-campo: #D8A3A6;   /* el borde del campo que no vale, y con el foco */
  --error-campo-foco: #C0787C;
  --bien: #1F5C4C;
  --bien-suave: #EDF6F2;
  --bien-borde: #C9E2D8;
  --bien-campo: #9CC7B6;
  --bien-campo-foco: #5E9C86;
  /* El aviso: el ámbar 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;
  --aviso-borde: #E9D8B4;

  /* Lo que va encima de la tinta (el texto de un botón sólido, la píldora del
     idioma activo) y los grises de en medio que no son ni fondo ni borde:
     las cabeceras de tabla y las filas al pasar, un borde que se marca al
     pasar por encima, el relleno de un hueco. */
  --sobre-tinta: #FFFFFF;
  --superficie-baja: #FBFAF7;
  --borde-fuerte: #D9D6CE;
  --relleno: #E9E7E1;
  --relleno-fuerte: #D5D3CC;

  /* El marco: el azul tinta de la marca como superficie —el pie, las
     tarjetas de proyecto, la caja del terminal— que lleva texto blanco encima.
     En claro es la propia tinta; en oscuro no puede serlo, que la tinta pasa a
     ser casi blanca, y se queda en un azul noche un punto por encima del fondo.
     El hondo es lo que queda por debajo de todo: el pie y el terminal. */
  --marco: #1B2230;
  --marco-fuerte: #12161F;
  --hondo: #1B2230;

  /* Las sombras, en canales sueltos para escribirlas con su opacidad de
     siempre (`rgb(var(--sombra) / .3)`), y el anillo del foco de los campos.
     La barra compacta es de cristal: el blanco casi opaco con el desenfoque
     de lo que pasa por debajo. */
  --sombra: 18 22 31;
  --anillo: rgba(27, 34, 48, .14);
  --anillo-fuerte: rgba(27, 34, 48, .2);
  --cristal: rgba(255, 255, 255, .88);
  --velo: rgb(var(--sombra) / .45);

  color-scheme: light dark;

  --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 tinta
     con un arranque de ámbar. Es el gesto propio de Nimbrook y va siempre igual
     —en la tarjeta del acceso, en las del repositorio—, así que se mide aquí. */
  --filo: 3px;
  --filo-ambar: 56px;

  --display: 'Space Grotesk', 'Inter', -apple-system, BlinkMacSystemFont,
             'Segoe UI', Arial, sans-serif;
  --sans: 'Inter', -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;
}

/* --------------------------------------------------------------------------
   Modo oscuro, el que pida el sistema.

   No es la paleta clara invertida: es la misma casa de noche. El fondo es la
   tinta de la marca (la del icono de la aplicación) y las superficies suben
   en luz cuanto más arriba están —fondo, tarjeta, hover—, porque a oscuras
   la sombra apenas se ve y lo que separa las capas es el tono. La tinta pasa
   a ser el blanco cálido del papel: los botones sólidos se dan la vuelta
   (papel con letra de tinta) y el texto no llega al blanco puro, que sobre
   negro deslumbra en una jornada de editor. El ámbar se queda como está: ya
   tiene contraste de sobra contra lo oscuro (7:1).

   Lo que es papel —las hojas A4 del editor, que enseñan lo que va a salir en
   el PDF— no cambia: sigue blanco, con sus tokens claros (ver detalle.css).
   -------------------------------------------------------------------------- */
@media (prefers-color-scheme: dark) {
  :root {
    --tinta: #E6E4DF;
    --tinta-fuerte: #F5F4F1;
    --tinta-tenue: #252A35;

    --texto: #DAD8D3;
    --texto-medio: #BABEC6;
    --texto-suave: #9DA2AC;
    --texto-tenue: #888E9A;   /* 5:1 sobre --tinta-tenue, lo más claro de abajo */

    --fondo: #0F1217;
    --superficie: #171B22;
    --borde: #2A2F39;
    --borde-suave: #222731;

    --error: #F0A0A4;
    --error-fuerte: #F6BABD;
    --error-suave: #2B181A;
    --error-borde: #4F2A2D;
    --error-campo: #6E3A3E;
    --error-campo-foco: #B76268;
    --bien: #7ECBAF;
    --bien-suave: #13241F;
    --bien-borde: #24463C;
    --bien-campo: #2F5D50;
    --bien-campo-foco: #5FA38B;
    --aviso: #E3B567;
    --aviso-texto: #E3B567;
    --aviso-suave: #2A2113;
    --aviso-borde: #4D3C1E;

    --sobre-tinta: #12161F;
    --superficie-baja: #1B2028;
    --borde-fuerte: #3A404C;
    --relleno: #2C323D;
    --relleno-fuerte: #3A404C;

    --marco: #252C39;
    --marco-fuerte: #1B2029;
    --hondo: #0A0C10;

    --sombra: 0 0 0;
    --anillo: rgba(230, 228, 223, .16);
    --anillo-fuerte: rgba(230, 228, 223, .24);
    --cristal: rgba(23, 27, 34, .86);
    --velo: rgba(0, 0, 0, .6);
  }
}

/* --------------------------------------------------------------------------
   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);
  /* Sobre el color del fondo va una teja: una retícula de rombos con un punto
     en cada cruce, con tan poca tinta que solo se nota en los huecos que dejan
     las superficies. Está construida en espejo, así que casa consigo misma por
     los cuatro lados y se repite sin costuras ni escalados; si la imagen no
     llega, debajo sigue estando el color de siempre. */
  background: var(--fondo) url("../images/patron.8289d923bcd4.png") repeat;
  background-size: 256px 256px;
  -webkit-font-smoothing: antialiased;
}

/* La teja es tinta sobre transparente y a oscuras no se vería: va la misma
   retícula en el blanco del papel, igual de leve. */
@media (prefers-color-scheme: dark) {
  body { background-image: url("../images/patron-oscuro.0225f77cd7ed.png"); }
}

/* 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 Inter. 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(--tinta); text-decoration: none; }
a:hover { color: var(--tinta-fuerte); 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(--tinta);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Sobre el marco —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 Nimbrook.

   Un hilo de tinta con el arranque en ámbar, 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-ambar),
    var(--tinta) var(--filo-ambar)
  );
}

/* --------------------------------------------------------------------------
   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;
}

/* La franja de un equipo en solo lectura (sin suscripción): bajo la cabecera,
   a todo lo ancho y en el pardo de los avisos, que no es un error. */
.franja-plan {
  flex-shrink: 0;
  margin: 0;
  padding: 10px var(--margen-lateral);
  background: var(--aviso-suave);
  border-bottom: 1px solid var(--aviso-borde);
  color: var(--aviso);
  font-size: 14px;
  line-height: 1.45;
}
.franja-plan a { margin-left: 6px; color: inherit; font-weight: 600; }

/* La marca: la N y el nombre, pegados. La N es la inicial de la aplicación
   con un arroyo ámbar por diagonal —el «brook» del nombre, dibujado—, y el
   nombre va en la letra de los titulares para que cabecera y titular hablen
   igual. Decorativa la imagen, el nombre es el texto. */
.marca {
  display: flex;
  align-items: center;
  gap: 12px;
  color: inherit;
  min-width: 0;
}
.marca:hover { text-decoration: none; }

.marca-icono { display: block; width: 36px; height: 36px; flex-shrink: 0; }
.marca picture { display: contents; }

.marca-nombre {
  font-family: var(--display);
  font-size: 25px;
  font-weight: 700;
  letter-spacing: -.015em;
  line-height: 1;
  color: var(--tinta-fuerte);
  white-space: nowrap;
}

/* ---- 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(--tinta-tenue);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--tinta);
  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
   lo cierra al pulsar fuera o con Escape, y lo baja y lo sube en persiana.
   El hueco entre el botón y la lista lo lee también menu.js: la persiana sale
   del borde del botón, no del de la lista. */
.menu { position: relative; flex-shrink: 0; --menu-hueco: 8px; }

.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]:not(.se-cierra) .menu__flecha { transform: rotate(180deg); }

.menu__lista {
  position: absolute;
  top: calc(100% + var(--menu-hueco));
  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 rgb(var(--sombra) / .12), 0 2px 6px rgb(var(--sombra) / .06);
  /* La rendija por la que sale la persiana (static/js/menu.js): nada asoma
     por encima del borde del botón, ni siquiera la sombra. Está también
     quieta y no solo mientras se mueve, para que al acabar de bajar no
     aparezca de golpe la sombra de arriba. Los lados y el fondo se pasan lo
     que ocupa la sombra, que si no se cortaría; si cambia, cambia también en
     menu.js. */
  clip-path: inset(calc(-1 * var(--menu-hueco)) -48px -56px);
}

/* Mientras sube ya está cerrado: no se puede pulsar lo que se va. */
.menu.se-cierra > .menu__lista { pointer-events: none; }

.menu__item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 9px 12px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--tinta-fuerte);
  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(--tinta-tenue); color: var(--tinta); 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__item--danger[aria-disabled="true"]:hover { background: none; }

.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 tema, al final del menú de la cuenta (static/js/tema.js): tres opciones
   en una cápsula y la elegida marcada con la píldora, como el selector de
   idioma. La píldora es una sola pieza que se desliza de una a otra; la
   posición se la da el tema puesto en <html>. */
.menu__tema { display: flex; flex-direction: column; }
.menu__tema[hidden] { display: none; }

.tema {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  margin: 2px 4px 4px;
  padding: 3px;
  border: 1px solid var(--borde);
  border-radius: 12px;
}

.tema__pildora {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 3px;
  width: calc((100% - 6px) / 3);
  border-radius: 9px;
  background: var(--tinta);
  transition: transform 260ms cubic-bezier(.32, .72, 0, 1);
}
:root[data-tema="claro"] .tema__pildora { transform: translateX(100%); }
:root[data-tema="oscuro"] .tema__pildora { transform: translateX(200%); }

/* Mientras el tema nuevo entra con el barrido (static/js/tema.js), nada
   lleva su propia transición: la página nueva es una instantánea viva, y lo
   que fundiera sus colores en 140 ms asomaría a medio camino por donde ya ha
   pasado el barrido. */
.tema-cambia,
.tema-cambia *,
.tema-cambia *::before,
.tema-cambia *::after { transition: none !important; }

.tema__opcion {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 7px 4px 6px;
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--texto-medio);
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color 140ms ease;
}
.tema__opcion:hover { color: var(--tinta-fuerte); }
.tema__opcion[aria-checked="true"] { color: var(--superficie); cursor: default; }
.tema__opcion:focus-visible { outline-offset: 1px; border-radius: 9px; }

.tema__opcion svg {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* 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(--tinta-fuerte);
  transition: background 140ms ease;
}
.cuenta-boton:hover,
.menu[open]:not(.se-cierra) > .cuenta-boton { background: var(--tinta-tenue); }

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

/* Que hay invitaciones esperando en la bandeja: un punto ámbar en la inicial,
   para que se vea sin abrir el menú, y la cifra en la fila de «Invitaciones». */
.cuenta-boton__inicial { position: relative; }
.cuenta-boton__aviso {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 10px;
  height: 10px;
  border: 2px solid var(--superficie);
  border-radius: 50%;
  background: var(--acento);
}

.menu__cifra {
  display: inline-grid;
  place-items: center;
  min-width: 20px;
  height: 20px;
  margin-left: auto;
  padding: 0 6px;
  border-radius: 10px;
  background: var(--aviso-suave);
  border: 1px solid var(--aviso-borde);
  color: var(--aviso-texto);
  font-size: 11.5px;
  font-weight: 700;
}

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

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

/* Selector de idioma: dos botones dentro de una cápsula, y el activo marcado
   con una píldora entera, como el de los manuales públicos
   (publico-idiomas.css). Entera y no media: al cambiar de idioma la píldora
   se desliza hasta el otro código, y una forma que es la misma en los dos
   sitios viaja sin que se le vean las esquinas. Cada botón 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;
  gap: 2px;
  padding: 2px;
  border: 1px solid var(--borde);
  border-radius: var(--radio-control);
}

.idioma-opcion {
  display: inline-flex;
  align-items: center;
  height: calc(var(--alto-control) - 6px);
  padding: 0 10px;
  border: 0;
  border-radius: var(--radio-control);
  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:hover { background: var(--tinta-tenue); color: var(--tinta-fuerte); }

/* El activo no se puede pulsar para nada (static/js/transicion-idioma.js no
   deja que recargue la página), 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(--tinta);
  color: var(--superficie);
  cursor: default;
}
.idioma-opcion.es-actual:hover { background: var(--tinta); color: var(--superficie); }

/* La transición al cambiar de idioma y al cambiar de tema. El idioma lo
   marca static/js/transicion-idioma.js, que le pone a la navegación el tipo
   `idioma` (al resto le dejan su fundido static/js/transiciones.js y el final
   de esta hoja); el tema, static/js/tema.js, con `.tema-cambia` en el <html>.
   La página nueva entra de izquierda a derecha sobre la vieja, con el sentido
   de la lectura: un barrido con el borde difuminado, de forma que el texto va
   cambiando poco a poco a su paso y el fondo, que es el mismo, ni se mueve.
   Al cambiar de idioma, encima, la píldora del idioma activo se desliza de un
   código al otro. Con «reducir movimiento» no hay transición: cambia de
   golpe, como antes. */

/* Durante el barrido no viaja nada con nombre propio por encima (la
   cabecera, el contenido, las filas del repositorio…): todo cambia a su paso
   a la vez que lo de alrededor. Solo el selector y la píldora del idioma, y
   solo cuando se cambia de idioma. */
html:is(.tema-cambia, :active-view-transition-type(idioma)) :is(.cabecera, .pie, .avisos, #contenido, .table__row--doc, .card) {
  view-transition-name: none;
}

html:active-view-transition-type(idioma) .idioma-opcion.es-actual { view-transition-name: idioma-actual; }

/* La vieja se queda quieta debajo y la nueva se descubre con una máscara: un
   degradado de opaco a transparente, ancho como la ventana más el borde
   difuminado (un 35 % de ella), que se corre de un lado al otro. Con ese
   ancho (235 %) el borde entra justo al empezar y sale justo al acabar, sin
   tiempo muerto. Es la misma textura de siempre en la GPU; lo único que se
   pinta en cada fotograma es la máscara. */
html:is(.tema-cambia, :active-view-transition-type(idioma))::view-transition-old(root) { animation: none; mix-blend-mode: normal; }
html:is(.tema-cambia, :active-view-transition-type(idioma))::view-transition-new(root) {
  animation: idioma-barrido 420ms cubic-bezier(.4, 0, .2, 1) both;
  mix-blend-mode: normal;
  mask-image: linear-gradient(to right, #000 42.55%, transparent 57.45%);
  mask-size: 235% 100%;
  mask-repeat: no-repeat;
}
@keyframes idioma-barrido {
  from { mask-position: 100% 0; }
  to { mask-position: 0 0; }
}

/* El selector va aparte del barrido: llega el último a la derecha, y la
   píldora dejaría un hueco detrás hasta que el barrido alcanzara el código
   que acaba de dejar. Suelto, sus dos códigos se funden a la vez que ella
   viaja (y ella, que es su descendiente, va por encima). Al cambiar de tema
   el selector va dentro del barrido, como todo lo demás: suelto se fundiría
   al oscuro antes que lo que lo rodea, y parecería que la píldora salta al
   otro idioma. */
html:active-view-transition-type(idioma) .idioma { view-transition-name: idioma-selector; }
::view-transition-group(idioma-selector) { animation-duration: 260ms; }

/* La píldora viaja recortada a su forma: sin el recorte, cada instantánea es
   un rectángulo con sus esquinas, y al fundirse en vuelo se verían. */
::view-transition-group(idioma-actual) {
  animation-duration: 420ms;
  animation-timing-function: cubic-bezier(.32, .72, 0, 1);
  clip-path: inset(0 round 999px);
}

/* --------------------------------------------------------------------------
   Pie: 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 {
  flex-shrink: 0;
  margin-top: auto;
  width: 100%;
  padding: 12px 0;
  background: var(--hondo);
  color: #FFFFFF;
  text-align: center;
}
/* A oscuras el pie queda por debajo del fondo, y sin el filo se juntaría con
   él en una sola mancha. */
@media (prefers-color-scheme: dark) {
  .pie { border-top: 1px solid var(--borde-suave); color: var(--texto-medio); }
  .pie a, .pie a:hover { color: var(--tinta); }
}
.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(--tinta);
  --aviso-suave: var(--tinta-tenue);
  --aviso-barra: var(--tinta);

  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 ámbar de la casa para la barra, que es un detalle; el icono va en un
   pardo más oscuro, que el ámbar sobre su fondo claro apenas se distingue. */
.aviso--warning { --aviso-tono: var(--aviso); --aviso-suave: var(--aviso-suave); --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; }

/* 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(--tinta-fuerte); background: var(--tinta-tenue); }
.aviso-cerrar:focus-visible { outline: 2px solid var(--tinta); 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(--tinta-fuerte);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: background 140ms ease;
}
.backlink:hover { background: var(--tinta-tenue); text-decoration: none; color: var(--tinta); }

.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 de ámbar corta y el rótulo en versalitas, el mismo
   gesto con el que se presentan todas las pantallas. */
.pagehead__eyebrow {
  display: flex;
  align-items: center;
  gap: 12px;
  font-size: 11.5px;
  font-weight: 700;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--tinta);
}
.pagehead__eyebrow::before {
  content: "";
  width: 34px;
  height: 2px;
  flex-shrink: 0;
  background: var(--acento);
}

.pagehead__heading {
  margin: 0;
  font-family: var(--display);
  font-size: 40px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -.025em;
  color: var(--tinta-fuerte);
  word-break: break-word;
}

.pagehead__sub {
  margin: 0;
  font-size: 14px;
  color: var(--texto-suave);
  overflow-wrap: anywhere;
}

.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;
}

/* ---- Pantallas donde rueda la página entera ----
   Las listas largas (los proyectos y lo de dentro de uno) 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: var(--cristal);
  -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 rgb(var(--sombra) / .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(--display);
  font-size: 21px;
  font-weight: 500;
  letter-spacing: -.02em;
  color: var(--tinta-fuerte);
  white-space: nowrap;
}
/* La misma regla de ámbar que el antetítulo: es el titular, en pequeño. */
.barra-compacta__titulo::before {
  content: "";
  width: 22px;
  height: 2px;
  background: var(--acento);
}

/* El nombre de un proyecto puede ser larguísimo: se corta antes que empujar
   la cuenta y el botón fuera de la barra. */
.barra-compacta__nombre { min-width: 0; overflow: hidden; text-overflow: ellipsis; }

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

.barra-compacta__accion { 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(--tinta);
  border-radius: var(--radio-control);
  background: var(--tinta);
  color: var(--sobre-tinta);
  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(--tinta-fuerte);
  border-color: var(--tinta-fuerte);
  color: var(--sobre-tinta);
  text-decoration: none;
}

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

.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(--tinta-fuerte);
  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(--tinta-tenue);
  border-color: var(--tinta-tenue);
  color: var(--tinta);
  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; }

/* La burbuja de los botones de icono: su nombre, debajo, al pasar por encima
   o al llegar con el teclado. Como .info__burbuja, en tinta; sale con un
   respiro para no parpadear al cruzar la barra con el ratón. `data-tip` y no
   `title`: el del navegador tarda un segundo y no se puede vestir. */
[data-tip] { position: relative; }
[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 8px);
  left: 50%;
  z-index: 30;
  width: max-content;
  max-width: 240px;
  padding: 6px 10px;
  border-radius: var(--radio-caja);
  background: var(--tinta-fuerte);
  color: var(--sobre-tinta);
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: normal;
  text-align: center;
  white-space: normal;
  box-shadow: 0 14px 32px -14px rgb(var(--sombra) / .5);
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translate(-50%, -3px);
  transition: opacity 120ms ease, transform 120ms ease, visibility 0s linear 120ms;
}
[data-tip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition-delay: 200ms;
}
@media (hover: hover) {
  [data-tip]:hover::after {
    opacity: 1;
    visibility: visible;
    transform: translate(-50%, 0);
    transition-delay: 200ms;
  }
}

.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(--tinta-fuerte);
}
/* Los de icono, en cambio, apagan el icono y no el botón: con la opacidad en
   el botón su burbuja (data-tip, arriba) saldría igual de desvaída. */
.button-ghost--icon[disabled],
.button-ghost--icon[aria-disabled="true"] { opacity: 1; }
/* (Con .button-ghost delante, para ganarle a .button-ghost:hover .button-icono.) */
.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(--tinta);
  box-shadow: 0 0 0 3px var(--anillo);
}
.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;
}
/* Y de texto corrido (la descripción de un proyecto): la letra del resto de
   campos. Se estira solo hacia abajo —de lado se saldría del diálogo— y hasta
   un tope que cabe en él con la descripción más larga entera a la vista. */
.field__input--texto {
  min-height: calc(2 * 1.5em + 20px);
  max-height: calc(8 * 1.5em + 20px);
  max-width: 100%;
  font-family: inherit;
  font-size: 15px;
}

.field.is-invalid .field__input,
.field__input[aria-invalid="true"] { border-color: var(--error-campo); }
.field.is-invalid .field__input:focus {
  border-color: var(--error-campo-foco);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--error) 14%, transparent);
}

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

/* La explicación plegada (templates/partials/info.html): un «i» pequeño junto
   al rótulo y una burbuja oscura que sale al pasar por encima, al enfocarlo o
   al tocarlo (el toque lo lleva static/js/info.js, con «is-abierta»). Para
   los diálogos con muchos ajustes, donde cada nota debajo de cada campo hacía
   un muro de texto gris. La burbuja se ancla al `.con-info`
   —el rótulo entero— y cabe en su ancho, así que no se sale del diálogo. */
.con-info {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.info { display: inline-flex; flex: 0 0 auto; }
.info__boton {
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  color: var(--texto-tenue);
  cursor: help;
  transition: color 140ms ease, background 140ms ease;
}
.info__boton svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.4;
  stroke-linecap: round;
}
.info__boton:hover,
.info__boton:focus-visible,
.info.is-abierta .info__boton { color: var(--tinta-fuerte); background: var(--tinta-tenue); }
.info__boton:focus-visible { outline: 2px solid var(--tinta); outline-offset: 1px; }
.info__burbuja {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  z-index: 20;
  width: max-content;
  max-width: min(300px, 100%);
  padding: 9px 12px;
  border-radius: var(--radio-caja);
  background: var(--tinta-fuerte);
  color: var(--sobre-tinta);
  font-size: 12.5px;
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: normal;
  text-transform: none;
  text-wrap: pretty;
  box-shadow: 0 14px 32px -14px rgb(var(--sombra) / .5);
  /* Que no reciba el ratón: dentro de un <label> un clic en ella cambiaría el
     interruptor, y así tampoco se queda abierta tapando el campo de debajo. */
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-3px);
  transition: opacity 120ms ease, transform 120ms ease, visibility 0s linear 120ms;
}
.info--arriba .info__burbuja { top: auto; bottom: calc(100% + 8px); transform: translateY(3px); }
.info:hover .info__burbuja,
.info__boton:focus-visible + .info__burbuja,
.info.is-abierta .info__burbuja {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}
.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: var(--bien-campo); }
.field.is-valid .field__input:focus {
  border-color: var(--bien-campo-foco);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--bien) 14%, transparent);
}
.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 (16 px
   como poco). 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 campo se guarda
   10 px de más abajo para que el renglón quepa entero en esa separación, con
   aire antes de lo siguiente. 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: var(--borde-fuerte); }
.picker__boton:focus-visible,
.picker.is-abierto .picker__boton {
  outline: none;
  border-color: var(--tinta);
  box-shadow: 0 0 0 3px var(--anillo);
}
.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(--tinta); }

.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 rgb(var(--sombra) / .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(--tinta-tenue); color: var(--tinta-fuerte); }
.picker__opcion[aria-selected="true"] { font-weight: 600; color: var(--tinta-fuerte); }

/* 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(--tinta);
  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): una caja con los tonos de error de la casa, anclada donde se
   ha fallado y no flotando en la esquina como los avisos de Django. */
.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; }

/* Los botones de un formulario, en fila y a la izquierda (los de la cuenta y
   los de allauth: guardar, pedir otro código, cancelar). */
.form__acciones { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; }

/* Una raya entre dos partes de un mismo formulario o tarjeta. */
.separador { width: 100%; height: 0; margin: 4px 0; border: 0; border-top: 1px solid var(--borde-suave); }

/* Una etiqueta pequeña junto a un dato: «verificado», «principal». */
.insignia {
  display: inline-flex;
  align-items: center;
  height: 20px;
  margin-left: 6px;
  padding: 0 8px;
  border: 1px solid var(--borde);
  border-radius: 10px;
  font-size: 11.5px;
  font-weight: 600;
  color: var(--texto-suave);
  vertical-align: 1px;
}
.insignia--bien { border-color: var(--bien-borde); background: var(--bien-suave); color: var(--bien); }
.insignia--aviso { border-color: var(--aviso-borde); background: var(--aviso-suave); color: var(--aviso-texto); }

.check__texto { font-size: 14px; line-height: 1.4; color: var(--texto); overflow-wrap: anywhere; }

.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(--tinta);
  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 rgb(var(--sombra) / .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: var(--superficie-baja);
  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(--tinta-tenue); text-decoration: none; color: inherit; }

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

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

.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(--tinta-fuerte); }
.table__label { font-size: 14.5px; }
.table__user { font-size: 14.5px; font-weight: 600; color: var(--tinta-fuerte); }
.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(--tinta-fuerte);
}

/* 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(--tinta-tenue); color: var(--tinta); 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(--tinta-fuerte); }
[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; }

@media (prefers-reduced-motion: reduce) {
  .rotulo-orden,
  .rotulo-orden__sube,
  .rotulo-orden__baja { transition: none; }
}

/* --------------------------------------------------------------------------
   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 de ámbar sobre
   blanco; en marcha, el gris que no compite; cerrado, la tinta sólida. */
.badge--generado { background: var(--superficie); color: var(--aviso-texto); border-color: var(--acento); }
.badge--borrador { background: transparent; color: var(--texto-tenue); border-color: var(--borde); }
.badge--revisado { background: var(--tinta); color: var(--sobre-tinta); border-color: var(--tinta); }

/* 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(--tinta-tenue); color: var(--tinta); border-color: var(--borde-fuerte); }

/* 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 rgb(var(--sombra) / .3);
  text-align: center;
}

.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(--display);
  font-size: 27px;
  font-weight: 500;
  letter-spacing: -.02em;
  color: var(--tinta-fuerte);
}

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

/* La ilustración de los huecos vacíos (images/sin-*.svg y compañía): el
   dibujo a mano de siempre, con el mismo tamaño en todas las pantallas. Es
   papel blanco con tinta oscura, también en modo oscuro. */
.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 { margin-bottom: -4px; }

/* --------------------------------------------------------------------------
   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(--tinta);
  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(--tinta); }

.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 -----------------------------------------------------
   Una píldora con las dos opciones (solo iconos) dentro, del alto de los botones vecinos. La
   puesta va en tinta sólida: es un estado, no un botón más. */

.viewswitch {
  flex-shrink: 0;
  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);
}

/* 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(--tinta-tenue); color: var(--tinta); text-decoration: none; }

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

.viewswitch__icono {
  width: 16px;
  height: 16px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* --- 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) {
  animation-duration: 420ms;
  animation-timing-function: cubic-bezier(.2, .8, .2, 1);
}

::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: 260ms;
}

/* 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; }

/* --- Paginación ---------------------------------------------------------------
   El repositorio y el selector de proyectos se sirven por páginas: sin esto,
   la portada cargaba en memoria todos los manuales de la instalación en cada
   visita. Solo se pinta cuando hay más de una página, así que en una
   instalación pequeña no se ve nunca. */

.paginacion {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 20px;
  margin-top: 28px;
}

.paginacion__estado {
  font-size: 13px;
  color: var(--texto-tenue);
}

.paginacion__salto {
  font-size: 13px;
  font-weight: 600;
  color: var(--tinta);
  white-space: nowrap;
}

/* --- 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(--tinta-tenue);
  color: var(--tinta);
  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] .paginacion {
  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 rgb(var(--sombra) / .45);
}
.dialogo::backdrop { background: var(--velo); }

/* 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(--display);
  font-size: 26px;
  font-weight: 500;
  letter-spacing: -.02em;
  color: var(--tinta-fuerte);
}

.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 var(--aviso-borde);
  border-radius: var(--radio-caja);
  background: var(--aviso-suave);
  color: var(--aviso);
}

/* La cifra del tope lleno (planes/_dialogo_tope.html): «3/3» en grande, en el
   pardo de los avisos, sobre su fondo ámbar y con la barra entera. */
.tope-cifra {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px;
  border: 1px solid var(--aviso-borde);
  border-radius: var(--radio-caja);
  background: var(--aviso-suave);
}

.tope-cifra__cabeza {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.tope-cifra__rotulo { font-size: 13px; font-weight: 600; line-height: 1.3; color: var(--aviso); }

.tope-cifra__chip {
  flex: none;
  padding: 2px 8px;
  border-radius: var(--radio-control);
  background: var(--acento);
  color: #1B1406;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.tope-cifra__numeros {
  margin: 0;
  display: flex;
  align-items: baseline;
  font-family: var(--display);
  font-variant-numeric: tabular-nums;
  line-height: 1;
  letter-spacing: -.02em;
}
.tope-cifra__numeros strong { font-size: 44px; font-weight: 500; color: var(--aviso); }
.tope-cifra__numeros span { font-size: 26px; font-weight: 500; color: var(--aviso-texto); opacity: .7; }

.tope-cifra__barra {
  display: block;
  height: 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--acento) 22%, transparent);
  overflow: hidden;
}
.tope-cifra__lleno { display: block; height: 100%; border-radius: inherit; background: var(--acento); }

.tope-cifra__nota { font-size: 12.5px; line-height: 1.45; color: var(--aviso-texto); }

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

/* Al abrirse, el diálogo sube un poco y se enciende, y el velo se funde; al
   cerrarse, lo mismo al revés. Todo con CSS, sin tocar ningún guion: los
   diálogos se abren y se cierran desde muchos sitios (showModal() y close()),
   y `@starting-style` da el punto de partida al abrir y `allow-discrete` deja
   que `display` y la capa de encima esperen a que acabe al cerrar. Un
   navegador que no lo sepa los abre y los cierra de golpe, como siempre. */
@media (prefers-reduced-motion: no-preference) {
  .dialogo {
    opacity: 0;
    transform: translateY(10px) scale(.985);
    transition:
      opacity 160ms ease,
      transform 200ms cubic-bezier(.2, .7, .3, 1),
      overlay 200ms allow-discrete,
      display 200ms allow-discrete;
  }
  .dialogo[open] { opacity: 1; transform: none; }
  .dialogo::backdrop {
    opacity: 0;
    transition: opacity 200ms ease, overlay 200ms allow-discrete, display 200ms allow-discrete;
  }
  .dialogo[open]::backdrop { opacity: 1; }
  @starting-style {
    .dialogo[open] { opacity: 0; transform: translateY(10px) scale(.985); }
    .dialogo[open]::backdrop { opacity: 0; }
  }
}

/* --------------------------------------------------------------------------
   Pestañas (static/js/pestanas.js)
   Para un diálogo con varios grupos de ajustes: una fila de rótulos con la
   raya del elegido debajo y, debajo, un panel cada vez. Los paneles van todos
   en la misma celda de una rejilla, así que la caja mide lo que el más alto y
   el diálogo no cambia de tamaño al pasar de uno a otro. Sin el guion no hay
   fila de pestañas y los paneles se ven uno debajo de otro, con su rótulo.
   -------------------------------------------------------------------------- */
.pestanas { display: flex; flex-direction: column; gap: 20px; min-width: 0; }
.pestanas__paneles { display: flex; flex-direction: column; gap: 28px; }

/* El aire de arriba y de los lados es para el anillo del foco, que la fila
   rueda en horizontal (en un móvil no caben las tres) y lo recortaría. */
.pestanas__lista {
  display: flex;
  gap: 22px;
  margin: -6px -10px 0;
  padding: 6px 10px 0;
  /* La raya de abajo, pintada y no de borde: así mide lo que los campos y no
     lo que el aire de los lados. */
  background: linear-gradient(var(--borde-suave), var(--borde-suave)) 10px 100% / calc(100% - 20px) 1px no-repeat;
  overflow-x: auto;
  scrollbar-width: none;
  --barra-propia: none; /* tampoco la propia (barra-desplazamiento.css) */
}
.pestanas__lista[hidden] { display: none; }
.pestanas__pestana {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: 0 0 auto;
  padding: 0 0 11px;
  border: 0;
  background: none;
  color: var(--texto-tenue);
  font-size: 13.5px;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
  transition: color 140ms ease;
}
.pestanas__pestana:hover { color: var(--tinta-fuerte); }
.pestanas__pestana[aria-selected="true"] { color: var(--tinta-fuerte); }
/* La raya del elegido: crece desde el centro al elegirla. */
.pestanas__pestana::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  border-radius: 2px;
  background: var(--tinta-fuerte);
  transform: scaleX(0);
  transition: transform 200ms cubic-bezier(.2, .7, .3, 1);
}
.pestanas__pestana[aria-selected="true"]::after { transform: scaleX(1); }
.pestanas__pestana:focus-visible { outline: none; }
.pestanas__pestana:focus-visible::before {
  content: "";
  position: absolute;
  inset: -3px -6px 6px;
  border-radius: 6px;
  box-shadow: 0 0 0 2px var(--tinta);
}

/* El punto de «encendido»: el índice publicado o la ayuda puesta, sin tener
   que abrir su pestaña para saberlo. Apagado no se ve. */
.pestanas__estado {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--bien);
  transform: scale(0);
  transition: transform 160ms ease;
}
.pestanas__pestana.is-encendida .pestanas__estado { transform: scale(1); }

.is-pestanas .pestanas__paneles { display: grid; }
.is-pestanas .pestanas__panel {
  grid-area: 1 / 1;
  visibility: hidden;
  opacity: 0;
  transform: translateY(4px);
  transition: opacity 160ms ease, transform 200ms cubic-bezier(.2, .7, .3, 1), visibility 0s linear 160ms;
}
.is-pestanas .pestanas__panel.is-activo {
  visibility: visible;
  opacity: 1;
  transform: none;
  transition-delay: 0s;
}
.is-pestanas .pestanas__rotulo { display: none; }

/* --------------------------------------------------------------------------
   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: 10px; }
  .marca-icono { width: 30px; height: 30px; }
  .marca-nombre { font-size: 21px; }
  .sesion-usuario { display: none; }

  .avisos { top: 92px; }

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

@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; }
  .content { overflow: visible; }
  .topbar { flex-wrap: wrap; min-height: 0; }
  .topbar__actions { margin-left: 0; flex-wrap: wrap; }
}

@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; }
  .pestanas__panel, .pestanas__pestana::after, .info__burbuja { 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);
}

@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, al cambiar de
   idioma—, 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. */
}
