/* Copiado de wsc-marca/src/estilos/base/heredado-componentes.css (4f300a7 2026-10-14) por web/traer-sistema.py. No se edita aquí: se cambia en el sistema y se vuelve a traer. Solo las secciones de los componentes de app, en este orden: barras-superiores, carril-navegacion, menu-lateral, barra-navegacion, pestanas, migas-paginacion, listas, tablas, separadores, dialogos, hojas-inferiores, hojas-laterales, avisos-pagina, avisos, globos, distintivos, indicadores-carga, indicadores-progreso, menus, barras-herramientas, botones-opcion, interruptor, chips, selector-fecha, campos, busqueda, casillas. */
/* ==== componentes/barras-superiores.css ==== */
/* ==========================================================================
   Barra superior: la primera fila de la vista, a todo su ancho y pegada arriba. Dice dónde
   estás (el logotipo, el título de la vista o lo que has elegido) y deja a mano lo que hace
   falta siempre: el menú, la búsqueda, volver, las acciones de lo elegido.
   Un marco y cuatro variantes. El marco es una fila de alto fijo con fondo y un filete inferior
   de 1 px en --linea; ninguna sombra, tampoco al bajar la página. No se esconde, no encoge y no
   cambia de color: lo que cambia es su contenido, en el mismo sitio y con el mismo alto.
     <header class="barra-superior barra-superior--producto">
       <div class="barra-superior-fila">
         <button type="button" class="barra-superior-boton barra-superior-menu" aria-label="Menú"
                 aria-expanded="false" aria-controls="menu">…i-barra-lateral…</button>
         <a class="barra-superior-marca" href="#inicio" aria-label="WSC®, inicio">
           <span class="logo" aria-hidden="true"></span></a>
         <p class="barra-superior-nombre">Sistema de diseño</p>
         <div class="barra-superior-fin">
           <form class="busqueda busqueda--s barra-superior-busqueda" role="search">…</form>
           <button type="button" class="barra-superior-boton barra-superior-lupa" aria-label="Buscar"
                   aria-haspopup="dialog">…i-buscar…</button>
           <div class="barra-superior-ajustes">…</div>
         </div>
       </div>
     </header>
   · Producto (.barra-superior--producto): la de las herramientas y aplicaciones. 56 de alto:
     un mando de 40 con 8 de aire (6 con el dedo). Va a los bordes de la vista y es simétrica:
     el botón del menú (i-barra-lateral) a 16 del borde, el logotipo a 16 del botón y a 28 de
     alto (la mitad de la barra: 4u arriba y abajo) y, desde M, el nombre del producto a 4u del
     logotipo (la mitad de su alto, 14). Al final, los mandos a 8 entre sí y a 16 del otro
     borde: la búsqueda (el campo S de Búsqueda, de 320, desde L; en S y M, la lupa, que la abre
     en su capa) y los ajustes (desde M; en S van en el menú).
   · Web (.barra-superior--web): la de wsc.design. 76 de alto (una zona de 44 con 16 arriba y
     abajo) y en la retícula: el logotipo en la línea A, a su medida de cabecera (74 de ancho en
     S, 93 desde M); la navegación desde la línea B (L; enlaces de 15 en texto 2, a 24 entre sí)
     y, al final, de izquierda a derecha: la hora de Madrid (desde 1180), el idioma (ES · EN,
     desde 1024) y el botón principal S, «Contacto», que acaba en el margen (desde M). En S y M,
     el botón del menú (i-menu) cierra la fila y la navegación va en su panel; en S, el botón
     principal también; por debajo de 1024, el idioma y la hora, al final del panel.
       <div class="barra-superior-fin">
         <p class="barra-superior-hora" data-web-hora hidden>…i-reloj…
           <time datetime="…" aria-label="Hora local en Madrid: 14:27">
             <span class="barra-superior-hora-cifra">14:27</span> <span class="barra-superior-hora-ciudad">Madrid</span></time></p>
         <ul class="barra-superior-idioma" aria-label="Idioma">
           <li><a href="#portada" hreflang="es" lang="es" aria-current="true">…ES…</a></li>
           <li><a href="#en" hreflang="en" lang="en">…EN…</a></li></ul>
         <a class="boton boton--s boton--primario barra-superior-accion" href="#contacto">Contacto</a>
         <button type="button" class="barra-superior-boton barra-superior-menu" …>…i-menu…</button>
       </div>
     La hora (.barra-superior-hora): el reloj de 20 y la ciudad en texto 2, las cifras en texto y
     tabulares, en menor (15/20), 24 h; a 16 del idioma. No es una región viva: cambia cada
     minuto sin anunciarse; su nombre accesible dice «Hora local en Madrid: 14:27». Sin JS no
     sale (hidden): sin reloj no hay hora que dar.
     El idioma (.barra-superior-idioma): dos enlaces a la otra versión (no botones), en menor y
     texto 2; el actual, en texto y 500 (aria-current), no solo en color. Cada uno responde en
     32 × 44 (44 × 44 con el dedo); su nombre empieza por lo que se ve y sigue con el idioma
     entero («ES, Español», «EN, English»: ES<span class="solo-lector">, Español</span>), en su
     lengua (lang, hreflang).
   · Contextual (.barra-superior--contextual): la de una vista de detalle en el móvil. Volver (un
     enlace con el chevron hacia la izquierda: .gira-90, el del sprite apunta abajo) en el sitio del
     menú; el título de la vista a 16 del botón (16/24, 500, una línea con puntos suspensivos) y
     una o dos acciones al final.
   · De selección (.barra-superior--seleccion): ocupa el sitio de la barra mientras hay algo
     elegido en una lista del móvil. Fondo --seleccion, el tinte de lo elegido; cerrar (i-cerrar,
     «Quitar la selección») en el sitio del menú, la cuenta en una región viva («3
     seleccionados») y sus acciones al final. Encima, el botón se aclara a --fondo.
   Botón de la barra (.barra-superior-boton, <button> o <a>): 40 × 40 (44 con el dedo), filete
   de 1 px en --linea-2 y radio 4, con el icono de 20 en texto 2: el mismo marco que los mandos
   de la derecha (campo, retícula, tema), así la barra empieza y acaba con piezas iguales. Lo
   identifica su icono (5,62:1), no el filete. Encima y pulsado, fondo de capa y el icono en el
   color del texto (pulsado, el icono baja 1 px); abierto (aria-expanded="true"), además, el
   filete en el color del texto, como el botón de un menú emergente.
   Enlace de la navegación (.barra-superior-enlace): la página actual (aria-current="page") va
   en el color del texto y en 500, con un filete de 1 px de acción sobre el de la barra, como la
   pestaña activa: no se marca solo con el color.
   Medidas en el ancho: fuera del contenido (la cabecera cuelga de body) se mide la ventana; dentro
   de una página (o de una .pagina-propia: una pantalla de la documentación), la página. Es la
   misma regla de la retícula (reticula.css) y los mismos cortes: M desde 640 y L desde 1024; en
   la web, además, la hora desde 1180 (el único corte propio: a 1024 no cabe junto a la navegación; a 1180, un iPad en horizontal, sí).
   ========================================================================== */
.barra-superior {
  --bs-alto: 56px;                                   /* la fila, sin el filete */
  --bs-boton: 40px;                                  /* 44 con el dedo */
  --bs-borde: var(--e-16);                           /* de los bordes de la vista a sus piezas */
  --bs-logo: calc(var(--bs-alto) / 2);               /* 28: la mitad de la barra */
  --bs-busqueda: 320px;
  --bs-encima: var(--capa);                          /* fondo de un botón encima, pulsado o abierto */
  --bs-arriba: env(safe-area-inset-top, 0px);        /* la isla y la muesca del teléfono */
  --bs-izquierda: env(safe-area-inset-left, 0px);
  --bs-derecha: env(safe-area-inset-right, 0px);
  position: sticky;
  inset-block-start: 0;
  z-index: 50;                                       /* la de la cabecera: lo que flota va debajo, salvo el diálogo modal */
  padding-block-start: var(--bs-arriba);
  border-block-end: var(--trazo) solid var(--linea);
  background: var(--fondo);
  color: var(--texto);
}
.barra-superior--web { --bs-alto: 76px; }
@media (any-pointer: coarse) {
  .barra-superior { --bs-boton: 44px; }
}

/* ---- La fila: a los bordes, con 8 entre pieza y pieza ---- */
.barra-superior-fila {
  display: flex;
  align-items: center;
  gap: var(--e-8);
  block-size: var(--bs-alto);
  padding-inline: max(var(--bs-borde), var(--bs-izquierda)) max(var(--bs-borde), var(--bs-derecha));
  /* Si no cabe (más acciones de la cuenta), el título se corta y lo que sobra no saca la página
     de la ventana: se recorta en el borde. Solo a lo ancho: la lista de la búsqueda cuelga debajo */
  overflow-x: clip;
}
/* Lo que sigue al botón de inicio (logotipo, título o cuenta), a 16 de él: 8 del hueco y 8 más */
.barra-superior-fila > .barra-superior-boton:first-child + * { margin-inline-start: var(--e-8); }

/* ---- El botón de la barra ---- */
.barra-superior-boton {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  box-sizing: border-box;
  inline-size: var(--bs-boton);
  block-size: var(--bs-boton);
  margin: 0;
  padding: 0;
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: transparent;
  color: var(--texto-2);
  font: inherit;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), border-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.barra-superior-boton > .i { transition: translate var(--m-t1) var(--m-ui); }
@media (hover: hover) {
  .barra-superior-boton:hover { background: var(--bs-encima); color: var(--texto); }
}
.barra-superior-boton.es-encima { background: var(--bs-encima); color: var(--texto); }
.barra-superior-boton:active,
.barra-superior-boton.es-pulsado { background: var(--bs-encima); color: var(--texto); }
.barra-superior-boton:active > .i,
.barra-superior-boton.es-pulsado > .i { translate: 0 1px; }
.barra-superior-boton[aria-expanded="true"] { border-color: var(--texto); background: var(--bs-encima); color: var(--texto); }

/* ---- Identidad: logotipo, nombre del producto, título o cuenta ---- */
.barra-superior-marca {
  display: flex;
  align-items: center;
  flex: none;
  min-block-size: var(--bs-boton);
  color: inherit;
  text-decoration: none;
}
.barra-superior--producto .logo { width: auto; height: var(--bs-logo); }
.barra-superior-nombre {
  display: none;
  flex: 0 1 auto;
  min-inline-size: 0;
  margin: 0;
  margin-inline-start: calc(var(--bs-logo) / 2 - var(--e-8));   /* a 4u del logotipo: 14 con el hueco */
  overflow: hidden;
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.barra-superior-titulo {
  flex: 0 1 auto;
  min-inline-size: 0;
  margin: 0;
  margin-inline-end: var(--e-8);                     /* 16 como poco hasta las acciones */
  overflow: hidden;
  color: var(--texto);
  font-size: 16px;                                   /* el cuerpo de la interfaz */
  line-height: 24px;
  font-weight: var(--p-medio);
  letter-spacing: -0.01em;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.barra-superior--seleccion .barra-superior-titulo { font-variant-numeric: tabular-nums; }

/* ---- El final: mandos y acciones, a 8 entre sí ---- */
.barra-superior-fin {
  display: flex;
  align-items: center;
  gap: var(--e-8);
  flex: none;
  margin-inline-start: auto;
}
.barra-superior-ajustes { display: none; align-items: center; gap: var(--e-8); }
/* La búsqueda: el campo S de Búsqueda, a la altura de los mandos y con su filete */
.barra-superior .barra-superior-busqueda { display: none; inline-size: var(--bs-busqueda); --bu-alto: var(--bs-boton); }
.barra-superior .busqueda-campo { border-color: var(--linea-2); }
/* Retícula y tema (conmutador y grupo de la base), a la altura de los mandos */
.barra-superior .conmutador { box-sizing: border-box; inline-size: var(--bs-boton); block-size: var(--bs-boton); }
.barra-superior .seg { box-sizing: border-box; block-size: var(--bs-boton); }
.barra-superior .seg button { block-size: calc(var(--bs-boton) - 2 * var(--trazo)); inline-size: calc(var(--bs-boton) - 2 * var(--trazo)); }
.barra-superior .seg .seg-texto { inline-size: auto; min-inline-size: calc(var(--bs-boton) - 2 * var(--trazo)); }
/* El progreso de lectura (.progreso-lectura, de la base) cae sobre el filete: la barra lo sitúa */

/* ---- Web: la fila es la retícula ---- */
.barra-superior--web .barra-superior-fila {
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  column-gap: var(--medianil);
  max-inline-size: var(--contenedor);
  margin-inline: auto;
  padding-inline: max(var(--margen), var(--bs-izquierda)) max(var(--margen), var(--bs-derecha));
}
.barra-superior--web .barra-superior-marca { grid-row: 1; grid-column: 1 / span 2; justify-self: start; min-block-size: 44px; }
.barra-superior--web .logo { inline-size: 74px; }
.barra-superior--web .barra-superior-nav { grid-row: 1; display: none; justify-self: start; }
.barra-superior--web .barra-superior-fin { grid-row: 1; grid-column: 3 / -1; justify-self: end; margin: 0; }
.barra-superior--web [hidden] { display: none !important; }
.barra-superior-accion { display: none; }
/* La hora de Madrid y el idioma: a la izquierda del botón principal; aparecen con el ancho */
.barra-superior-hora {
  display: none;
  align-items: center;
  gap: var(--e-8);
  margin: 0;
  margin-inline-end: var(--e-16);
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  white-space: nowrap;
}
.barra-superior-hora > .i { flex: none; }
.barra-superior-hora-cifra { color: var(--texto); font-variant-numeric: tabular-nums; }
.barra-superior-idioma { display: none; align-items: center; margin: 0; padding: 0; list-style: none; }
.barra-superior-idioma a {
  display: inline-grid;
  place-items: center;
  box-sizing: border-box;
  min-inline-size: 32px;
  min-block-size: 44px;
  padding-inline: var(--e-4);
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: 0.02em;
  text-decoration: none;
  transition: color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .barra-superior-idioma a:hover { color: var(--texto); }
}
.barra-superior-idioma a.es-encima { color: var(--texto); }
.barra-superior-idioma a[aria-current] { color: var(--texto); font-weight: var(--p-medio); }
@media (any-pointer: coarse) {
  .barra-superior-idioma a { min-inline-size: 44px; }
}
.barra-superior-nav ul { display: flex; gap: var(--e-24); margin: 0; padding: 0; list-style: none; }
.barra-superior-enlace {
  position: relative;
  display: flex;
  align-items: center;
  min-block-size: 44px;
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .barra-superior-enlace:hover { color: var(--texto); }
}
.barra-superior-enlace.es-encima { color: var(--texto); }
.barra-superior-enlace[aria-current] { color: var(--texto); font-weight: var(--p-medio); }
/* El filete de la página actual cae sobre el de la barra: la zona de 44 está centrada en la fila */
.barra-superior-enlace[aria-current]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc((var(--bs-alto, 44px) - 44px) / -2 - var(--trazo));
  block-size: var(--trazo);
  background: var(--accion);
}

/* ---- De selección: el tinte de lo elegido ---- */
.barra-superior--seleccion {
  --bs-encima: var(--fondo);
  border-block-end-color: var(--seleccion);
  background: var(--seleccion);
}

/* ---- Lo que cambia con el ancho ----
   Fuera del contenido, la ventana; dentro de una página, la página (sin repetir los cortes) */
@container ventana (min-width: 640px) {
  .barra-superior:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-nombre { display: block; }
  .barra-superior:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-ajustes { display: flex; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .logo { inline-size: 93px; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-marca { grid-column: 1 / span 3; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-fin { grid-column: 4 / -1; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-accion { display: inline-flex; }
}
@container ventana (min-width: 1024px) {
  .barra-superior:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-busqueda { display: block; }
  .barra-superior:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-fin:has(.barra-superior-busqueda) .barra-superior-lupa { display: none; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-nav { display: block; grid-column: 5 / -1; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-fin { grid-column: 5 / -1; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-menu { display: none; }
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-idioma { display: flex; }
}
/* La hora, desde 1180: por debajo no cabe junto a la navegación (y en S y M va en el panel) */
@container ventana (min-width: 1180px) {
  .barra-superior--web:where(:not(.lienzo *, .pagina-propia *)) .barra-superior-hora { display: flex; }
}
@container pagina (min-width: 640px) {
  .barra-superior .barra-superior-nombre { display: block; }
  .barra-superior .barra-superior-ajustes { display: flex; }
  .barra-superior--web .logo { inline-size: 93px; }
  .barra-superior--web .barra-superior-marca { grid-column: 1 / span 3; }
  .barra-superior--web .barra-superior-fin { grid-column: 4 / -1; }
  .barra-superior--web .barra-superior-accion { display: inline-flex; }
}
@container pagina (min-width: 1024px) {
  .barra-superior .barra-superior-busqueda { display: block; }
  .barra-superior .barra-superior-fin:has(.barra-superior-busqueda) .barra-superior-lupa { display: none; }
  .barra-superior--web .barra-superior-nav { display: block; grid-column: 5 / -1; }
  .barra-superior--web .barra-superior-fin { grid-column: 5 / -1; }
  .barra-superior--web .barra-superior-menu { display: none; }
  .barra-superior--web .barra-superior-idioma { display: flex; }
}
@container pagina (min-width: 1180px) {
  .barra-superior--web .barra-superior-hora { display: flex; }
}

/* Movimiento reducido: los cambios de color, al momento */
@media (prefers-reduced-motion: reduce) {
  .barra-superior-boton, .barra-superior-boton > .i, .barra-superior-enlace, .barra-superior-idioma a { transition: none; }
}
@media (forced-colors: active) {
  .barra-superior, .barra-superior--seleccion { border-block-end-color: CanvasText; }
  .barra-superior-boton { border-color: ButtonText; color: ButtonText; }
  .barra-superior-boton[aria-expanded="true"] { border-color: Highlight; background: Highlight; color: HighlightText; }
  .barra-superior .logo { forced-color-adjust: none; background: CanvasText; }
  .barra-superior-enlace[aria-current]::after { background: Highlight; }
  .barra-superior-idioma a[aria-current] { color: CanvasText; text-decoration: underline; text-underline-offset: var(--e-4); }
}
/* En papel, la barra va una vez, arriba */
@media print {
  .barra-superior { position: static; }
}

/* ==== componentes/carril-navegacion.css ==== */
/* ==========================================================================
   Carril de navegación: la navegación principal de un producto en el tramo M (640–1023).
   Una columna fija a la izquierda, de 80 con su filete, con 3 a 7 destinos del mismo nivel:
   cada uno, su icono de 24 y su nombre debajo. En S la navegación pasa a la barra de
   navegación (abajo); en L, al menú lateral. Los destinos, el orden y los iconos son los
   mismos en los tres tramos.
     <nav class="carril" aria-label="Principal">
       <div class="carril-cabeza">
         <button class="carril-menu" type="button" aria-label="Menú"
           aria-expanded="false" aria-controls="menu-lateral">
           <svg class="i i-24" aria-hidden="true"><use href="#i-barra-lateral"/></svg></button>
       </div>
       <ul class="carril-destinos">
         <li><a class="carril-destino" href="/proyectos" aria-current="page">
           <span class="carril-icono"><svg class="i i-24" aria-hidden="true"><use href="#i-proyecto"/></svg></span>
           <span class="carril-nombre">Proyectos</span></a></li>
         <li><a class="carril-destino" href="/mensajes">
           <span class="carril-icono distintivo-ancla"><svg class="i i-24" aria-hidden="true"><use href="#i-chat"/></svg>
             <span class="distintivo" aria-hidden="true">3</span></span>
           <span class="carril-nombre">Mensajes<span class="solo-lector distintivo-lector"
             data-uno=", {n} sin leer" data-varios=", {n} sin leer">, 3 sin leer</span></span></a></li>
       </ul>
     </nav>
   · Carril: 80 de ancho con el filete derecho de 1 px en --linea; fondo de la página. Ocupa
     el alto de lo que lo contiene: quien lo coloca le da la pantalla (una rejilla de dos
     columnas, 80 y el resto, o position: sticky arriba). Con el móvil apaisado, la zona
     segura se suma por fuera: el dibujo no se mete bajo la muesca.
   · Cabeza (opcional): 56 de alto, la de la barra superior de al lado, con el botón de menú
     (.carril-menu) de 40 × 40 e icono de 24 en su centro: cae en la fila de la barra. Abre
     el menú lateral (aria-controls, aria-expanded; js/componentes/carril-navegacion.js).
     Abierto, fondo de capa. Con el dedo, responde en 48 × 48.
   · Destinos (.carril-destinos): la lista, con 8 de aire arriba y abajo; si no cabe en
     alto (el móvil apaisado), se desliza y la cabeza se queda. Cada destino
     (.carril-destino) mide 79 × 64 (80 menos el filete): icono de 24, 4 de hueco y el
     nombre en --t-nota (13/16) debajo, centrados en el eje de la columna, la única
     alineación que vale en 80. El nombre no se parte: cabe en 71 (79 − 2 × 4).
   · Actual (aria-current="page"; "true" en el destino de la sección cuando la página está
     por debajo): icono y nombre en el color de acción, el nombre en 500 y un filete de
     1 px a su izquierda, a todo el alto del destino, en el mismo color. Tres señales: el
     color no va solo.
   · Encima: fondo de capa e icono y nombre en el color del texto (el actual sigue en
     acción). Pulsado: fondo de capa y el icono baja 1 px. Foco: el anillo común, por
     dentro del destino (los destinos se tocan y el anillo no pisa al vecino).
   · Distintivo (opcional): hay algo que atender. Es el de Distintivos
     (css/componentes/distintivos.css), sobre el icono: .carril-icono lleva también
     .distintivo-ancla y el .distintivo (con cifra, o .distintivo--punto sin ella) se centra
     en su esquina, con 2 de aire en el color del fondo. Encima y pulsado, el carril da al
     aire el fondo del destino (--di-aire: capa). El que se ve lleva aria-hidden: el lector
     oye la frase de .distintivo-lector dentro del nombre («Mensajes, 3 sin leer»).
   Ningún destino se deshabilita: al que no se puede ir no está en el carril.
   ========================================================================== */
.carril {
  --ca-ancho: 80px;
  --ca-cabeza: 56px;
  --ca-boton: 40px;
  --ca-destino: 64px;
  --ca-icono: 24px;
  --ca-color: var(--texto-2);
  --ca-color-encima: var(--texto);
  --ca-actual: var(--accion);
  --ca-encima: var(--capa);
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  inline-size: calc(var(--ca-ancho) + env(safe-area-inset-left, 0px));
  block-size: 100%;
  min-block-size: 0;
  margin: 0;
  padding-inline-start: env(safe-area-inset-left, 0px);
  border-inline-end: var(--trazo) solid var(--linea);
  background: var(--fondo);
  color: var(--texto);
}
/* En RTL el carril va a la derecha: la zona segura es la de ese lado */
.carril:dir(rtl) {
  inline-size: calc(var(--ca-ancho) + env(safe-area-inset-right, 0px));
  padding-inline-start: env(safe-area-inset-right, 0px);
}

/* ---- Cabeza: el botón de menú, en la fila de la barra superior ---- */
.carril-cabeza {
  display: grid;
  place-items: center;
  flex: none;
  block-size: var(--ca-cabeza);
}
.carril-menu {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--ca-boton);
  block-size: var(--ca-boton);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--texto);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
.carril-menu .i { inline-size: var(--ca-icono); block-size: var(--ca-icono); transition: translate var(--m-t1) var(--m-ui); }
/* Abierto: fondo de capa mientras el menú lateral está a la vista */
.carril-menu[aria-expanded="true"] { background: var(--ca-encima); }

/* ---- Destinos ---- */
.carril-destinos {
  display: grid;
  align-content: start;
  flex: 1 1 auto;
  min-block-size: 0;
  margin: 0;
  padding: var(--e-8) 0 calc(var(--e-8) + env(safe-area-inset-bottom, 0px));
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}
.carril-destinos > li { margin: 0; padding: 0; min-inline-size: 0; }
.carril-destino {
  position: relative;
  display: grid;
  justify-items: center;
  align-content: center;
  gap: var(--e-4);
  min-block-size: var(--ca-destino);
  padding-inline: var(--e-4);
  color: var(--ca-color);
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
/* El filete del actual: 1 px a la izquierda, a todo el alto del destino. Transparente en los demás */
.carril-destino::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: 0;
  inline-size: var(--trazo);
  background: transparent;
}
.carril-icono {
  position: relative;
  display: block;
  inline-size: var(--ca-icono);
  block-size: var(--ca-icono);
  transition: translate var(--m-t1) var(--m-ui);
}
.carril-icono > .i { display: block; inline-size: var(--ca-icono); block-size: var(--ca-icono); }
.carril-nombre {
  max-inline-size: 100%;
  overflow: hidden;
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* Actual: la página donde estás, o la sección que la contiene */
.carril-destino[aria-current]:not([aria-current="false"]) { color: var(--ca-actual); }
.carril-destino[aria-current]:not([aria-current="false"])::before { background: var(--ca-actual); }
.carril-destino[aria-current]:not([aria-current="false"]) .carril-nombre { font-weight: var(--p-medio); }

/* Lo que se quita con hidden (la cabeza) no ocupa sitio */
.carril-cabeza[hidden] { display: none; }

/* ---- Encima y pulsado. .es-encima y .es-pulsado enseñan el estado quieto en la documentación ---- */
@media (hover: hover) {
  .carril-menu:hover { background: var(--ca-encima); }
  .carril-destino:hover { --di-aire: var(--ca-encima); background: var(--ca-encima); color: var(--ca-color-encima); }
  .carril-destino[aria-current]:not([aria-current="false"]):hover { color: var(--ca-actual); }
}
.carril-menu.es-encima { background: var(--ca-encima); }
.carril-destino.es-encima { --di-aire: var(--ca-encima); background: var(--ca-encima); color: var(--ca-color-encima); }
.carril-destino.es-encima[aria-current]:not([aria-current="false"]) { color: var(--ca-actual); }
.carril-menu:active,
.carril-menu.es-pulsado { background: var(--ca-encima); }
.carril-menu:active .i,
.carril-menu.es-pulsado .i { translate: 0 1px; }
.carril-destino:active,
.carril-destino.es-pulsado { --di-aire: var(--ca-encima); background: var(--ca-encima); }
.carril-destino:active:not([aria-current]),
.carril-destino.es-pulsado:not([aria-current]) { color: var(--ca-color-encima); }
.carril-destino:active .carril-icono,
.carril-destino.es-pulsado .carril-icono { translate: 0 1px; }

/* ---- Foco: el anillo común; en el destino, por dentro (los destinos se tocan) ---- */
.carril-destino:focus-visible,
.carril-destino.es-foco {
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: var(--r);
}
.carril-destino:focus-visible { z-index: 1; }

/* ---- Con el dedo: el botón de menú responde en 48 × 48 sin crecer (los destinos ya miden 64) ---- */
@media (any-pointer: coarse) {
  .carril-menu::after { content: ""; position: absolute; inset: calc(-1 * var(--e-4)); }
}

/* ---- Movimiento reducido: sin recorridos ---- */
@media (prefers-reduced-motion: reduce) {
  .carril-menu, .carril-menu .i, .carril-destino, .carril-icono { transition: none; }
}

/* ---- Colores forzados: el actual, con el color de resalte del sistema ---- */
@media (forced-colors: active) {
  .carril { border-inline-end-color: CanvasText; }
  .carril-destino[aria-current]:not([aria-current="false"])::before { forced-color-adjust: none; background: Highlight; }
  .carril-destino[aria-current]:not([aria-current="false"]) .carril-nombre { overflow: visible; text-decoration: underline; text-underline-offset: 2px; }
  .carril-menu[aria-expanded="true"] { outline: var(--trazo) solid CanvasText; }
}

/* ---- Al imprimir, la navegación no va ---- */
@media print {
  .carril { display: none; }
}

/* ==== componentes/menu-lateral.css ==== */
/* ==========================================================================
   Menú lateral: la navegación principal de un producto (una herramienta, un portal) con
   muchas páginas, a la izquierda de la página. Categorías con su rótulo, temas que se
   despliegan (dos niveles) y la página actual marcada. El mismo botón lo abre y lo cierra.
     <button type="button" class="menu-lateral-boton" aria-controls="menu-portal"
             aria-expanded="true" aria-label="Cerrar el menú" hidden>
       <svg class="i i-20" aria-hidden="true"><use href="#i-barra-lateral"/></svg>
     </button>                                            (en la cabecera del producto)
     <div class="menu-lateral-marco">
       <nav class="menu-lateral" id="menu-portal" aria-label="Portal">
         <ul class="menu-lateral-lista">
           <li><a class="menu-lateral-fila" href="/">Inicio</a></li>
           <li class="menu-lateral-grupo">
             <p class="t-rotulo menu-lateral-rotulo" aria-hidden="true">Proyecto</p>
             <ul aria-label="Proyecto">
               <li><a class="menu-lateral-fila" href="/plan">Plan de trabajo</a></li>
               <li><details class="menu-lateral-tema" open>
                 <summary class="menu-lateral-fila"><svg class="i i-16" aria-hidden="true">
                   <use href="#i-chevron"/></svg>Branding estratégico</summary>
                 <ul class="menu-lateral-sub">
                   <li><a class="menu-lateral-fila" href="/identidad" aria-current="page">Identidad visual</a></li>
                 </ul></details></li>
             </ul>
           </li>
           <li class="menu-lateral-grupo menu-lateral-grupo--final">…</li>
         </ul>
       </nav>
       <div class="menu-lateral-contenido">… la página …</div>
     </div>
   · Fijo (desde 1280 de ventana: 1024 de página, que sigue en L, más 256 de menú): una columna
     de 256 junto a la página, pegada bajo la cabecera y con su propio desplazamiento. El botón
     lo cierra y lo abre (data-estado="cerrado" | "abierto" en el marco): la columna se anima
     entera (--ml-visible) y la página se aparta a la vez. Siempre empieza abierto.
   · En panel (por debajo de 1280): cerrado; el botón lo saca bajo la cabecera, sobre un velo,
     y la página queda inerte (data-flota="panel" en el menú).
   · Asomado: cerrado y con ratón, al pasar por el botón sale con el aspecto del abierto, sin
     velo (data-flota="asoma"); se esconde al salir y un clic lo deja abierto.
   · .menu-lateral-grupo--final: cuenta y ayuda, al final y tras un filete.
   · .menu-lateral-cuenta (opcional): lo pendiente de una página, en cifras, al final de su fila.
   · En un tema, el chevron va delante del nombre en el HTML: flota al final de la primera línea
     y un nombre largo pasa a la segunda sin empujarlo.
   Filas de 32 (44 con el dedo), en texto 2; encima, en texto. La actual: fondo de capa, texto
   500 y filete de 1 en el color de acción a la izquierda (aria-current="page"). El texto de
   las filas y el de los rótulos caen en la misma vertical, a 16 del borde: 15 de relleno y
   el filete de 1 de la actual. Subfilas a 32. Sin iconos: en el escritorio el nombre basta.
   El menú no es de la página: se mide en la ventana, como la cabecera (@container ventana, el
   mismo corte que el menú de la herramienta). Fijo, mide lo que queda de la ventana bajo la
   cabecera (100dvh menos --ml-arriba) y se desplaza por su cuenta.
   En panel, con un velo que cubre la ventana, la página de detrás no se desplaza
   (html[data-menu-lateral-panel], que pone y quita el JS).
   Sin JS: fijo desde 1280 y, por debajo, encima de la página, entero; el botón no se ve
   (lleva hidden y el JS se lo quita). Los temas son <details>: se despliegan sin JS.
   ========================================================================== */
@property --ml-visible { syntax: "<length>"; inherits: true; initial-value: 0px; }

/* Los tokens van en el marco (los leen la columna, el menú y el velo) o en un menú suelto */
.menu-lateral-marco,
.menu-lateral:not(.menu-lateral-marco *) {
  --ml-ancho: 256px;                   /* 16 × 16 */
  --ml-lado: var(--e-16);              /* texto a 16 del borde */
  --ml-sangria: var(--e-32);           /* las páginas de un tema */
  --ml-fila: 32px;                     /* alto de fila: 20 de línea y 6 arriba y abajo */
  --ml-linea: 20px;
  /* Dónde empieza: bajo la cabecera del producto (56 y su filete). Quien lo usa lo cambia si
     su cabecera mide otra cosa. El alto, lo que queda de la ventana */
  --ml-arriba: calc(env(safe-area-inset-top, 0px) + 56px + var(--trazo));
  --ml-alto: calc(100dvh - var(--ml-arriba));
}
.menu-lateral-marco { --ml-visible: 0px; --ml-cabe: 0; }

/* ---- El menú ---- */
.menu-lateral {
  display: block;
  box-sizing: border-box;
  background: var(--fondo);
  color: var(--texto);
  /* Sin JS y por debajo de 1280 va encima de la página, a todo el ancho */
  border-block-end: var(--trazo) solid var(--linea);
}
.menu-lateral ul { margin: 0; padding: 0; list-style: none; }
.menu-lateral-lista {
  padding-block: var(--e-8) var(--e-32);
  font-size: var(--t-menor);
  line-height: var(--ml-linea);
  letter-spacing: -0.01em;
}

/* La fila: un enlace, o el <summary> de un tema. Toda la fila responde */
.menu-lateral-fila {
  display: flex;
  align-items: center;
  gap: var(--e-8);
  box-sizing: border-box;
  inline-size: 100%;
  margin: 0;
  padding-block: calc((var(--ml-fila) - var(--ml-linea)) / 2);
  /* 15 + el filete de 1: el texto, a 16 del borde, en la vertical de los rótulos */
  padding-inline: calc(var(--ml-lado) - var(--trazo)) var(--ml-lado);
  border: 0;
  border-inline-start: var(--trazo) solid transparent;
  background: none;
  color: var(--texto-2);
  font: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
}
/* El anillo del foco, por dentro: la columna recorta lo que sale */
.menu-lateral-fila:focus-visible,
.menu-lateral-fila.es-foco { outline: var(--trazo-foco) solid var(--foco); outline-offset: calc(-1 * var(--trazo-foco)); border-radius: var(--r); }
@media (hover: hover) { .menu-lateral-fila:hover { color: var(--texto); } }
.menu-lateral-fila.es-encima { color: var(--texto); }
/* La página actual. En una página de tercer nivel (que el menú no enseña), la que la contiene,
   igual (aria-current="true") */
.menu-lateral-fila[aria-current] {
  background: var(--capa);
  color: var(--texto);
  font-weight: var(--p-medio);
  border-inline-start-color: var(--accion);
}
.menu-lateral-sub .menu-lateral-fila { padding-inline-start: calc(var(--ml-sangria) - var(--trazo)); }

/* Lo pendiente de una página (opcional): una cifra al final, en texto 2 */
.menu-lateral-cuenta {
  flex: none;
  margin-inline-start: auto;
  color: var(--texto-2);
  font-weight: var(--p-normal);
  font-variant-numeric: tabular-nums;
}

/* ---- Tema: se despliega con su chevron. En bloque (no en flex: <summary> no lo admite en
   todos los Safari), con el chevron flotando al final, a 16 del borde. Nada posicionado
   dentro del menú: Safari deja sin pintar lo posicionado dentro de un panel fijo que vive
   en un contenedor con contain ---- */
.menu-lateral-tema > summary {
  display: block;
  list-style: none;
}
.menu-lateral-tema > summary::-webkit-details-marker { display: none; }
.menu-lateral-tema > summary > .i {
  float: right;
  margin-block: calc((var(--ml-linea) - 16px) / 2);
  margin-inline-start: var(--e-8);
  color: var(--texto-2);
}
:dir(rtl) .menu-lateral-tema > summary > .i { float: left; }
.menu-lateral-tema[open] > summary > .i { rotate: 180deg; }
/* Plegado, el tema que contiene la página actual lo dice: su nombre en texto 500, sin fondo */
.menu-lateral-tema:not([open]):has([aria-current]) > summary { color: var(--texto); font-weight: var(--p-medio); }

/* ---- Categorías: el rótulo no navega. Su lista lleva el mismo nombre en aria-label y el rótulo,
   aria-hidden (en mayúsculas, el lector lo deletrearía) ---- */
.menu-lateral-rotulo { margin: 0; padding: var(--e-24) var(--ml-lado) var(--e-8); color: var(--texto-2); }
/* Grupo final (cuenta, ayuda): 24 bajo lo anterior, tras un filete de 16 a 16 y 8 de aire */
.menu-lateral-grupo--final { margin-block-start: var(--e-24); }
.menu-lateral-grupo--final::before {
  content: "";
  display: block;
  margin: 0 var(--ml-lado) var(--e-8);
  border-block-start: var(--trazo) solid var(--linea);
}
.menu-lateral-grupo--final > .menu-lateral-rotulo { padding-block-start: var(--e-16); }

/* ---- El marco: menú | página ---- */
.menu-lateral-contenido { min-inline-size: 0; }
/* Con JS, por debajo de 1280 el menú solo se ve cuando flota (panel o asomado) */
.menu-lateral-marco[data-estado] > .menu-lateral:not([data-flota]) { display: none; }

/* Desde 1280 de ventana cabe fijo: la columna mide --ml-visible (256 abierto, 0 cerrado) y se
   anima con él; el menú entra deslizándose desde la izquierda y la página se aparta a la vez */
@container ventana (min-width: 1280px) {
  .menu-lateral-marco { --ml-cabe: 1; display: grid; grid-template-columns: var(--ml-visible) minmax(0, 1fr); }
  .menu-lateral-marco:not([data-estado="cerrado"]) { --ml-visible: var(--ml-ancho); }
  .menu-lateral-marco > .menu-lateral,
  .menu-lateral-marco[data-estado] > .menu-lateral:not([data-flota]) {
    display: block;
    grid-column: 1;
    grid-row: 1;
    position: sticky;
    inset-block-start: var(--ml-arriba);
    align-self: start;
    block-size: var(--ml-alto);
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    border-block-end: 0;
    border-inline-end: var(--trazo) solid var(--linea);
  }
  .menu-lateral-marco > .menu-lateral > .menu-lateral-lista {
    inline-size: calc(var(--ml-ancho) - var(--trazo));
    translate: calc(var(--ml-visible) - var(--ml-ancho)) 0;
  }
  :dir(rtl) .menu-lateral-marco > .menu-lateral > .menu-lateral-lista { translate: calc(var(--ml-ancho) - var(--ml-visible)) 0; }
  /* Cerrado, no se ve ni recibe el foco cuando acaba de salir */
  .menu-lateral-marco[data-estado="cerrado"] > .menu-lateral:not([data-flota]) { visibility: hidden; transition: visibility 0s linear var(--m-t3); }
  .menu-lateral-marco > .menu-lateral-contenido { grid-column: 2; grid-row: 1; }
}

/* ---- Flota: en panel o asomado, a cualquier ancho. Bajo la cabecera, a toda la altura, con
   el filete de fin; entra desde la izquierda (receta del panel lateral: t5 con la curva de
   trazos) y sale en t3 con la de salida. El panel lleva velo; el asomado, no ---- */
.menu-lateral-marco > .menu-lateral[data-flota] {
  display: block;
  position: fixed;
  z-index: 45;
  inset-block: var(--ml-arriba) 0;
  inset-inline-start: 0;
  inline-size: calc(var(--ml-ancho) + env(safe-area-inset-left, 0px));
  max-inline-size: calc(100% - var(--e-48));
  block-size: auto;
  padding-inline-start: env(safe-area-inset-left, 0px);
  padding-block-end: env(safe-area-inset-bottom, 0px);
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  visibility: visible;
  border-block-end: 0;
  border-inline-end: var(--trazo) solid var(--linea);
}
.menu-lateral-marco > .menu-lateral[data-flota] > .menu-lateral-lista { inline-size: auto; translate: none; }
/* Siempre con traslación (0 en reposo): así el menú es el bloque de sus filas y Safari las pinta
   también cuando el panel vive dentro de un contenedor con contain (una pantalla de muestra) */
.menu-lateral-marco > .menu-lateral[data-flota] { translate: 0 0; }
.menu-lateral-marco > .menu-lateral[data-paso] { translate: -100% 0; }
:dir(rtl) .menu-lateral-marco > .menu-lateral[data-paso] { translate: 100% 0; }
.menu-lateral-velo { position: fixed; z-index: 44; inset: var(--ml-arriba) 0 0; background: var(--velo); }
/* Con el panel abierto sobre la ventana, la página de detrás no se desplaza */
html[data-menu-lateral-panel] { overflow: hidden; scrollbar-gutter: stable; }

/* ---- El botón: el mismo abre y cierra, a cualquier ancho. 40 × 40 con filete de línea 2, como
   los mandos de la cabecera; abierto, con fondo de capa (no de acción: el verde es para los
   modos) ---- */
.menu-lateral-boton {
  --ml-mando: 40px;
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  box-sizing: border-box;
  inline-size: var(--ml-mando);
  block-size: var(--ml-mando);
  margin: 0;
  padding: 0;
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: transparent;
  color: var(--texto-2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.menu-lateral-boton[hidden] { display: none; }
.menu-lateral-boton[aria-expanded="true"] { background: var(--capa); color: var(--texto); }
@media (hover: hover) { .menu-lateral-boton:hover { background: var(--capa); color: var(--texto); } }
.menu-lateral-boton.es-encima { background: var(--capa); color: var(--texto); }

/* ---- Movimiento: solo con las llaves (html[data-mov] y sin «reducir movimiento») ---- */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .menu-lateral-marco { transition: --ml-visible var(--m-t3) var(--m-va); }
  html[data-mov] .menu-lateral-marco:not([data-estado="cerrado"]) { transition: --ml-visible var(--m-t5) var(--m-traza); }
  html[data-mov] .menu-lateral-marco > .menu-lateral[data-flota] { transition: translate var(--m-t5) var(--m-traza); }
  html[data-mov] .menu-lateral-marco > .menu-lateral[data-paso="sale"] { transition: translate var(--m-t3) var(--m-va); }
  html[data-mov] .menu-lateral-marco > .menu-lateral[data-paso="entra"] { transition: none; }
  html[data-mov] .menu-lateral-tema > summary > .i { transition: rotate var(--m-t3) var(--m-ui); }
}

/* ---- Con el dedo: filas de 44 (la fila entera es la zona: crecen, no se solapan) y el botón
   responde en 44 sin crecer ---- */
@media (any-pointer: coarse) {
  .menu-lateral-marco, .menu-lateral:not(.menu-lateral-marco *) { --ml-fila: 44px; }
  .menu-lateral-boton::before { content: ""; position: absolute; inset: calc((var(--ml-mando) - 44px) / 2 - var(--trazo)); }
}

@media (forced-colors: active) {
  .menu-lateral-fila[aria-current] { border-inline-start: var(--trazo-foco) solid Highlight; }
  .menu-lateral-marco > .menu-lateral[data-flota], .menu-lateral-boton { border-color: CanvasText; }
  .menu-lateral-velo { background: transparent; }
}

@media print {
  .menu-lateral, .menu-lateral-boton, .menu-lateral-velo { display: none !important; }
  .menu-lateral-marco { display: block !important; }
}

/* ==== componentes/barra-navegacion.css ==== */
/* ==========================================================================
   Barra de navegación: los destinos principales de un producto, en el móvil.
   Un nav con su lista de enlaces, fijo abajo y a todo el ancho: funciona sin JS y el
   teclado y el lector son los de cualquier enlace.
     <main class="barra-navegacion-hueco">…</main>
     <nav class="barra-navegacion" aria-label="Portal de cliente">
       <ul class="barra-navegacion-lista">
         <li><a class="barra-navegacion-destino" href="/proyectos" aria-current="page">
           <svg class="i i-24" aria-hidden="true"><use href="#i-proyecto"/></svg>
           <span class="barra-navegacion-texto">Proyectos</span></a></li>
         <li><a class="barra-navegacion-destino" href="/mensajes">
           <span class="distintivo-ancla"><svg class="i i-24" aria-hidden="true"><use href="#i-chat"/></svg>
             <span class="distintivo" aria-hidden="true">2</span></span>
           <span class="barra-navegacion-texto">Mensajes<span class="solo-lector distintivo-lector"
             data-uno=", {n} sin leer" data-varios=", {n} sin leer">, 2 sin leer</span></span></a></li>
       </ul>
     </nav>
   · De tres a cinco destinos, en columnas iguales: con cinco, un quinto del ancho cada uno
     (64 a 320 px). Cada destino responde en toda su columna y 64 de alto.
   · Solo en el tramo S (@container pagina, menos de 640): desde 640 la barra no se pinta y
     su sitio lo toma el carril de navegación (M) o el menú lateral (L), con los mismos
     destinos en el mismo orden. Por eso va dentro del contenedor de la página.
   · Fija abajo: fondo del de la página y filete superior de 1 px en --linea (el de la
     cabecera, arriba). Debajo de los destinos, la zona segura del teléfono
     (env(safe-area-inset-bottom), con viewport-fit=cover). z-index 40: bajo la cabecera (50).
   · .barra-navegacion-hueco, en el contenido (o en lo que se desplaza): deja abajo el alto
     de la barra y la zona segura, para que lo último no quede tapado, y lo que recibe el foco
     con Tab se para a 16 por encima de la barra.
   Destino: icono de 24 y nombre en --t-nota (13 / 16) a 4 debajo; el bloque (44) centrado en
   los 64 de alto: 10 arriba y 10 abajo. Reposo: icono y nombre en texto 2 (5,62:1).
   Actual (aria-current="page"; "true" dentro de su vista, en el detalle de un proyecto): en
   el color de acción, el nombre en 500 y un filete de 1 px en acción sobre el de la barra,
   del ancho del nombre, como la pestaña activa.
   Distintivo (opcional): lo pendiente del destino, con el de Distintivos
   (css/componentes/distintivos.css) sobre el icono: .distintivo-ancla envuelve el icono y el
   .distintivo va en su esquina, con 2 de aire en el color del fondo. La barra lo baja 2
   (--di-baja): el icono está a 10 de su filete y, centrado en la esquina, el aire lo tocaría.
   Pulsado, el aire toma la capa de la placa (--di-aire). La frase para el lector, en el
   nombre (.distintivo-lector): el enlace dice «Mensajes, 2 sin leer».
   Estados: encima, icono y nombre en el color del texto (el actual, un paso de acción);
   pulsado, una placa en --capa a 4 de los bordes de la columna; foco, el anillo común por
   dentro de la columna. Ningún destino se deshabilita.
   ========================================================================== */
.barra-navegacion,
.barra-navegacion-hueco {
  --bn-alto: 64px;
  --bn-icono: 24px;
  --bn-hueco: var(--e-4);
  --bn-linea: 16px;
  /* Del borde de arriba de la columna al icono: lo que sobra del bloque (44), a partes iguales */
  --bn-arriba: calc((var(--bn-alto) - var(--bn-icono) - var(--bn-hueco) - var(--bn-linea)) / 2);
  --bn-zona-segura: env(safe-area-inset-bottom, 0px);
  /* Lo que la barra tapa abajo: sus 64, su filete y la zona segura */
  --bn-tapa: calc(var(--bn-alto) + var(--trazo) + var(--bn-zona-segura));
}
.barra-navegacion {
  position: fixed;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: 40;
  margin: 0;
  padding-block: 0 var(--bn-zona-segura);
  padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px);
  border-block-start: var(--trazo) solid var(--linea);
  background: var(--fondo);
  color: var(--texto-2);
}
.barra-navegacion[hidden] { display: none; }
.barra-navegacion-lista {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* El destino: el enlace ocupa su columna entera (la zona que responde) */
.barra-navegacion-destino {
  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--bn-hueco);
  block-size: var(--bn-alto);
  padding-block-start: var(--bn-arriba);
  border-radius: var(--r);
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: var(--bn-linea);
  letter-spacing: 0;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
  /* El distintivo, 2 más abajo que la esquina del icono: su aire no toca el filete de la barra */
  --di-baja: 2px;
}
.barra-navegacion-destino > .i,
.barra-navegacion-destino > .distintivo-ancla > .i { inline-size: var(--bn-icono); block-size: var(--bn-icono); }
/* El nombre, en una línea: nunca se parte ni se corta (el largo lo cuida el contenido) */
.barra-navegacion-texto {
  position: relative;
  min-inline-size: var(--bn-icono);
  white-space: nowrap;
  text-align: center;
}
/* La placa del pulsado: por detrás, a 4 de los bordes de la columna */
.barra-navegacion-destino::before {
  content: "";
  position: absolute;
  inset: var(--e-4);
  z-index: -1;
  border-radius: var(--r);
  background: transparent;
  transition: background-color var(--m-t1) var(--m-ui);
}

/* Actual: el color de acción, el nombre en 500 y el filete sobre el de la barra, del ancho del
   nombre y en el color del destino (encima, sigue al nombre). Sube desde el nombre lo que hay
   hasta el borde: el icono, el hueco, el aire de arriba y el filete de la barra */
.barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]) { color: var(--accion); }
.barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]) .barra-navegacion-texto { font-weight: var(--p-medio); }
.barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]) .barra-navegacion-texto::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(-1 * (var(--bn-arriba) + var(--bn-icono) + var(--bn-hueco) + var(--trazo)));
  block-size: var(--trazo);
  background: currentColor;
}

/* Encima (con puntero) y pulsado. .es-encima y .es-pulsado enseñan el estado quieto en la
   documentación */
@media (hover: hover) {
  .barra-navegacion-destino:hover { color: var(--texto); }
  .barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]):hover { color: var(--accion-hover); }
}
.barra-navegacion-destino.es-encima { color: var(--texto); }
.barra-navegacion-destino.es-encima:is([aria-current="page"], [aria-current="true"]) { color: var(--accion-hover); }
.barra-navegacion-destino:active::before,
.barra-navegacion-destino.es-pulsado::before { background: var(--capa); }
/* Pulsado, el aire del distintivo es de la placa */
.barra-navegacion-destino:active,
.barra-navegacion-destino.es-pulsado { --di-aire: var(--capa); }

/* Foco: el anillo común, por dentro de la columna (la barra está en el borde de la pantalla) */
.barra-navegacion-destino:focus-visible,
.barra-navegacion-destino.es-foco { outline-offset: calc(-1 * var(--trazo-foco)); }

/* El contenido deja sitio a la barra; lo que recibe el foco con Tab se para a 16 por encima de
   ella, con su anillo entero a la vista */
.barra-navegacion-hueco {
  padding-block-end: var(--bn-tapa);
  scroll-padding-block-end: calc(var(--bn-tapa) + var(--e-16));
}
.barra-navegacion-hueco :is(a, button, input, select, textarea, summary, [tabindex]) { scroll-margin-block-end: calc(var(--bn-tapa) + var(--e-16)); }

/* Solo en S: desde 640 la navegación es el carril (M) o el menú lateral (L) */
@container pagina (min-width: 640px) {
  .barra-navegacion { display: none; }
  .barra-navegacion-hueco { padding-block-end: 0; scroll-padding-block-end: 0; }
  .barra-navegacion-hueco :is(a, button, input, select, textarea, summary, [tabindex]) { scroll-margin-block-end: 0; }
}

/* Movimiento reducido: los cambios, al instante */
@media (prefers-reduced-motion: reduce) {
  .barra-navegacion-destino, .barra-navegacion-destino::before { transition: none; }
}
@media (forced-colors: active) {
  .barra-navegacion { border-block-start-color: CanvasText; }
  .barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]) { color: Highlight; }
  .barra-navegacion-destino:is([aria-current="page"], [aria-current="true"]) .barra-navegacion-texto::before { forced-color-adjust: none; background: Highlight; }
}
@media print {
  .barra-navegacion { display: none; }
  .barra-navegacion-hueco { padding-block-end: 0; }
}

/* ==== componentes/pestanas.css ==== */
/* ==========================================================================
   Pestañas: vistas hermanas, del mismo nivel, de las que se ve una cada vez. Una fila de
   textos sobre un filete de 1 px; la activa, en texto 500 con un filete de 1 px en el color de
   acción solo bajo su texto; las demás, en texto 2. La misma fila vale para dos usos:
   · De navegación: cada pestaña es una página. Enlaces en un nav; la de la página que se ve,
     con aria-current="page" (lo pone quien sirve la página). Funcionan sin JS: pestanas.js solo
     coloca la fila con la activa a la vista y funde el lado que esconde pestañas.
       <nav class="fila-pestanas" aria-label="Trabajo">
         <ul class="fila-pestanas-lista">
           <li><a class="fila-pestanas-pestana" href="/proyectos" aria-current="page">
             <span class="fila-pestanas-texto">Proyectos</span></a></li>
           <li><a class="fila-pestanas-pestana" href="/servicios">
             <span class="fila-pestanas-texto">Servicios</span></a></li>
         </ul>
       </nav>
   · De contenido: cada pestaña enseña su panel, en la misma página. Se escriben como enlaces a
     los paneles (data-pestanas en la fila): sin JS son un índice y se ven todos los paneles.
     pestanas.js los convierte en el patrón de pestañas (tablist, tab, tabpanel, aria-selected,
     flechas con activación automática, Inicio y Fin, una sola parada de Tab) y esconde los
     paneles que no tocan.
       <div class="fila-pestanas" data-pestanas>
         <ul class="fila-pestanas-lista" aria-label="Casa Ceiba">
           <li><a class="fila-pestanas-pestana" href="#ceiba-resumen">
             <span class="fila-pestanas-texto">Resumen</span></a></li> …
         </ul>
       </div>
       <div class="fila-pestanas-panel" id="ceiba-resumen">…</div> …
   · Recuento (opcional): cuántos hay en esa vista, tras el texto. Es el distintivo de recuento
     (.distintivo .distintivo--recuento, css/componentes/distintivos.css): 16 de alto, en capa,
     con la cifra a 12 en 500 y cifras tabulares, en el color de la pestaña (texto 2; texto en
     la activa y encima). La pestaña lo pone a 8 del texto; él se centra con la mayúscula.
       <a class="fila-pestanas-pestana" href="/proyectos" aria-current="page">
         <span class="fila-pestanas-texto">Proyectos</span>
         <span class="distintivo distintivo--recuento">14</span></a>
   · .fila-pestanas--sangra: en S, la fila entra en el margen de la página y llega a los bordes
     de la pantalla, como las pestañas de esta herramienta: se desliza sin cortarse a 24 del borde.
   Estados en ARIA: aria-current (navegación) y aria-selected (contenido). Encima, pulsado y foco,
   también quietos para la documentación (.es-encima, .es-pulsado, .es-foco).
   Medidas y por qué:
   · Pestaña de 44 de alto en S y 48 en M y L (la zona del dedo y la del botón M), con el texto
     de 15/20 centrado. 32 entre pestañas: la separación la hace el aire, no una caja.
   · Cada pestaña lleva 4 de aire a cada lado, con margen negativo (la separación sigue siendo 32):
     el anillo del foco va por dentro (la fila recorta lo que sale) y no toca la letra. El filete
     de la activa va sin ese aire: solo bajo el texto (y el recuento).
   · El filete de la activa pisa el de la fila (margen de −1 abajo): una sola línea, de otro color.
   · El texto reserva el ancho del 500 (pestanas.js escribe data-texto y un ::after invisible lo
     mide): al cambiar de activa, ninguna pestaña se mueve.
   · Si no cabe, la fila se desliza en horizontal, en una sola línea (nunca salta a dos). El lado
     que esconde pestañas se funde en 24 más lo que la fila entra en el margen (data-mas: «izq»,
     «der», pestanas.js). Al entrar, la activa queda a la vista y lo que recibe el foco sale del
     fundido. Sin sombra ni flecha: un fundido de la propia fila.
   · Pulsado: el filete asoma en gris de control bajo la pestaña que se va a abrir (en el móvil,
     sin encima, es la respuesta al dedo mientras llega la página).
   ========================================================================== */
.fila-pestanas {
  --fp-alto: 44px;
  --fp-separacion: var(--e-32);
  --fp-aire: var(--e-4);
  --fp-sangrado: 0px;      /* lo que la fila entra en el margen de su contenedor (.fila-pestanas--sangra) */
  --fp-fundido: var(--e-24);
  --fp-lado: max(var(--fp-sangrado), var(--fp-aire));
  position: relative;
  display: block;
  min-inline-size: 0;
  /* Es un contenedor que se desplaza: en una rejilla o una columna flex apretadas encoge por
     debajo de su alto y corta las pestañas. Con este mínimo, nunca baja de una pestaña */
  min-block-size: var(--fp-alto);
  margin-inline: calc(-1 * var(--fp-lado));
  padding-inline: var(--fp-lado);
  overflow-x: auto;
  overflow-y: hidden;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  /* Lo que llega con Tab o con las flechas se queda fuera del fundido */
  scroll-padding-inline: calc(var(--fp-lado) + var(--fp-fundido));
}
.fila-pestanas::-webkit-scrollbar { display: none; }
@container pagina (min-width: 640px) {
  .fila-pestanas { --fp-alto: 48px; }
}
@container pagina (max-width: 639px) {
  .fila-pestanas--sangra { --fp-sangrado: max(var(--margen), env(safe-area-inset-left, 0px), env(safe-area-inset-right, 0px)); }
}

/* La lista: una línea, con el filete de la fila debajo, a todo el ancho aunque sobre sitio */
.fila-pestanas-lista {
  display: flex;
  gap: var(--fp-separacion);
  inline-size: max-content;
  min-inline-size: 100%;
  margin: 0;
  padding: 0;
  list-style: none;
  border-bottom: var(--trazo) solid var(--linea);
}
.fila-pestanas-lista > li { display: flex; flex: none; }

/* La pestaña */
.fila-pestanas-pestana {
  --fp-filete: var(--accion);
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--e-8);
  box-sizing: border-box;
  block-size: var(--fp-alto);
  margin: 0 calc(-1 * var(--fp-aire)) calc(-1 * var(--trazo));
  padding: 0 var(--fp-aire);
  border: 0;
  border-radius: 0;
  background: linear-gradient(var(--fp-filete), var(--fp-filete)) 50% 100% / 0 var(--trazo) no-repeat;
  color: var(--texto-2);
  font: inherit;
  font-size: var(--t-menor);
  font-weight: var(--p-normal);
  line-height: 20px;
  letter-spacing: -0.01em;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
}
/* El texto guarda el ancho que tendría en 500: el ::after, invisible y sin alto, lo mide */
.fila-pestanas-texto { display: inline-grid; }
.fila-pestanas-texto[data-texto]::after {
  content: attr(data-texto);
  content: attr(data-texto) / "";
  block-size: 0;
  overflow: hidden;
  visibility: hidden;
  font-weight: var(--p-medio);
  pointer-events: none;
  user-select: none;
}

/* Activa: texto 500 en el color del texto y el filete de acción bajo el texto (sin el aire) */
.fila-pestanas-pestana:is([aria-current]:not([aria-current="false"]), [aria-selected="true"]) {
  color: var(--texto);
  font-weight: var(--p-medio);
  background-size: calc(100% - 2 * var(--fp-aire)) var(--trazo);
  cursor: default;
}
/* Un enlace a la página que ya se ve sigue siendo un enlace: vuelve arriba de ella */
a.fila-pestanas-pestana[aria-current]:not([aria-current="false"]) { cursor: pointer; }

/* Encima (con puntero): el texto pasa al color del texto. Pulsado: además, el filete asoma en
   gris de control. .es-encima y .es-pulsado enseñan el estado quieto en la documentación */
@media (hover: hover) {
  .fila-pestanas-pestana:hover { color: var(--texto); }
}
.fila-pestanas-pestana.es-encima { color: var(--texto); }
.fila-pestanas-pestana:not([aria-current]:not([aria-current="false"]), [aria-selected="true"]):active,
.fila-pestanas-pestana.es-pulsado:not([aria-current]:not([aria-current="false"]), [aria-selected="true"]) {
  --fp-filete: var(--linea-control);
  color: var(--texto);
  background-size: calc(100% - 2 * var(--fp-aire)) var(--trazo);
}

/* Foco: el anillo común, por dentro de la pestaña (la fila recorta lo que sale) */
.fila-pestanas-pestana:focus-visible,
.fila-pestanas-pestana.es-foco { outline-offset: calc(-1 * var(--trazo-foco)); border-radius: var(--r); }

/* El lado que esconde pestañas se funde. La máscara solo lee el alfa */
.fila-pestanas[data-mas] {
  --fp-fi: 0px;
  --fp-fd: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent, black var(--fp-fi), black calc(100% - var(--fp-fd)), transparent);
  mask-image: linear-gradient(to right, transparent, black var(--fp-fi), black calc(100% - var(--fp-fd)), transparent);
}
.fila-pestanas[data-mas~="izq"] { --fp-fi: calc(var(--fp-lado) + var(--fp-fundido)); }
.fila-pestanas[data-mas~="der"] { --fp-fd: calc(var(--fp-lado) + var(--fp-fundido)); }

/* El panel (de contenido): a 24 de la fila. El que no toca, fuera (hidden, pestanas.js) */
.fila-pestanas-panel { padding-block-start: var(--e-24); min-inline-size: 0; }
.fila-pestanas-panel[hidden] { display: none; }

/* Con el dedo: 44 de alto ya los tiene; a lo ancho, la zona crece hasta la mitad del hueco
   (12 a cada lado), para que una pestaña corta («Web») también mida 44 */
@media (any-pointer: coarse) {
  .fila-pestanas-pestana { position: relative; }
  .fila-pestanas-pestana::before {
    content: "";
    position: absolute;
    inset-block: 0;
    inset-inline: calc(var(--fp-aire) - var(--fp-separacion) / 2);
  }
}

/* Movimiento reducido: el color cambia al instante */
@media (prefers-reduced-motion: reduce) {
  .fila-pestanas-pestana { transition: none; }
}
/* Colores forzados: el degradado del filete no se pinta; la activa, con 2 px en el color del sistema */
@media (forced-colors: active) {
  .fila-pestanas-lista { border-bottom-color: CanvasText; }
  .fila-pestanas-pestana:is([aria-current]:not([aria-current="false"]), [aria-selected="true"]) {
    border-block-end: var(--trazo-foco) solid Highlight;
  }
}

/* ==== componentes/migas-paginacion.css ==== */
/* ==========================================================================
   Migas y paginación: navegación en texto. Lo que se pulsa va en texto 2 (5,62:1); lo actual,
   en el color del texto. Dos piezas, cada una con su raíz (.migas y .paginacion), hechas de
   enlaces: no necesitan JS.

   MIGAS: dónde está la página y el camino hacia arriba. Un nav con su lista ordenada; la
   última es la página que se ve, sin enlace y con aria-current="page".
     <nav class="migas" aria-label="Migas de pan">
       <ol class="migas-lista">
         <li><a class="migas-enlace" href="/">Inicio</a><span class="migas-sep" aria-hidden="true">›</span></li>
         <li><a class="migas-enlace" href="/proyectos/"><svg class="i i-16 gira-90 migas-volver"
           aria-hidden="true"><use href="#i-chevron"/></svg> <span class="migas-volver">Volver a </span>Proyectos</a><span
           class="migas-sep" aria-hidden="true">›</span></li>
         <li><span class="migas-actual" aria-current="page">Havet</span></li>
       </ol>
     </nav>
   · 13 / 20. El separador «›», en texto 3 (3,64:1: es un signo, no texto que haya que leer),
     a 8 de cada lado y fuera del lector. Va al final de cada paso: si la fila no cabe, el
     separador cierra la línea y la siguiente empieza por un nombre.
   · Encima (con puntero): el enlace pasa a texto y se subraya con 1 px a 4 de la base.
   · En S (@container pagina, menos de 640) solo queda el padre (el penúltimo), con el chevron
     hacia la izquierda y «Volver a»: «‹ Volver a Proyectos». Por eso el enlace del padre lleva
     los dos, que en M y L no se pintan. El chevron de 16 tiene 6 de aire hasta su punta: un
     margen de −6 la pone en la línea del texto de debajo.
   · Con el dedo, cada enlace responde en 44 de alto (20 y 12 arriba y abajo) y 8 más a cada
     lado, hasta el separador, sin mover nada.

   PAGINACIÓN: una lista larga en páginas, cada una con su dirección (?p=5). Un nav con
   «Anterior», el estado (para el lector), los números y «Siguiente».
     <nav class="paginacion" aria-label="Paginación">
       <a class="paginacion-anterior" href="?p=4" rel="prev" aria-label="Página anterior">
         <svg class="i i-16 gira-90" aria-hidden="true"><use href="#i-chevron"/></svg>
         <span class="paginacion-texto">Anterior</span></a>
       <p class="paginacion-estado">Página 5 de 12</p>
       <ul class="paginacion-lista">
         <li><a class="paginacion-numero" href="?p=1" aria-label="Página 1">1</a></li>
         <li class="paginacion-salto" aria-hidden="true">…</li>
         <li><a class="paginacion-numero" href="?p=5" aria-label="Página 5" aria-current="page">5</a></li> …
       </ul>
       <a class="paginacion-siguiente" href="?p=6" rel="next" aria-label="Página siguiente">
         <span class="paginacion-texto">Siguiente</span>
         <svg class="i i-16 gira-270" aria-hidden="true"><use href="#i-chevron"/></svg></a>
     </nav>
   · Siete huecos como mucho: la primera y la última, la actual con sus dos vecinas y «…» donde
     faltan páginas; cerca de un extremo, los números de ese lado (1 2 3 4 5 … 12). La fila de
     números no cambia de ancho al pasar de página.
   · Cada hueco es una casilla de 44 × 44 (la zona del dedo, también con el puntero), con la
     cifra de 15 / 20 en el centro, en cifras tabulares. Sin cajas: separa el aire.
   · Actual (aria-current="page"): texto 500 y un filete de 1 px en el color de acción al pie de
     la casilla, de 20 de ancho (44 − 2 × 12), como la pestaña activa.
   · «Anterior» y «Siguiente»: chevron de 16 (el símbolo apunta abajo: .gira-90 lo vuelve a la
     izquierda y .gira-270 a la derecha) y texto, a 4 más los 6 de aire del chevron, con 12 a los
     lados. «Anterior» entra 18 en el margen (12 + 6): la punta cae en la línea del texto de
     encima. En la primera página no hay «Anterior» y en la última no hay «Siguiente»: lo que no
     lleva a ninguna parte no se pinta, ni apagado.
   · En S, los números dejan su sitio al estado («Página 5 de 12», en texto) y «Anterior» y
     «Siguiente» se quedan en su chevron, en casillas de 44 × 44; su nombre, el de aria-label.
   · Encima: texto. Pulsado: además, el filete asoma en gris de control (en el móvil, sin
     encima, es la respuesta al dedo mientras llega la página). Foco: el anillo común, por dentro
     de la casilla, para no pisar a las vecinas.
   .paginacion--mas: «Cargar más» (el botón secundario) y el recuento («12 de 48 artículos», en
     13 / 16 y texto 2, con role="status"). Para la revista, que se lee seguido. El botón lo mueve
     quien carga la lista: «Cargando…» con aria-busy, el foco al primer artículo de la tanda y, con
     todo a la vista, fuera.
       <div class="paginacion paginacion--mas">
         <button class="boton boton--secundario" type="button">Cargar más</button>
         <p class="paginacion-recuento" role="status">12 de 48 artículos</p>
       </div>
   .paginacion--cadena: «Anterior · siguiente» entre artículos, con el título del destino, como
     el pie de esta herramienta: dos mitades a un medianil (apiladas en S), con un filete de 1 px
     en línea 2 encima. El siguiente va siempre en la segunda mitad (rel="next"). Encima, el
     filete pasa al color de acción y el título al de enlace.
       <nav class="paginacion paginacion--cadena" aria-label="Anterior y siguiente">
         <a class="paginacion-destino" href="…" rel="prev">
           <span class="paginacion-destino-rotulo">Anterior</span>
           <span class="paginacion-destino-titulo">Branding para emprendedores</span></a>
         <a class="paginacion-destino" href="…" rel="next">…</a>
       </nav>
   Estados en ARIA (aria-current, aria-busy). Encima, pulsado y foco, también quietos para la
   documentación (.es-encima, .es-pulsado, .es-foco).
   ========================================================================== */

/* ---- Migas ---- */
.migas {
  --mi-sep: var(--e-8);
  --mi-sangria: 6px; /* aire del chevron de 16 hasta su punta: 9 de 24 en el dibujo */
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: 20px;
  letter-spacing: 0;
}
.migas-lista {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  column-gap: var(--mi-sep);
  margin: 0;
  padding: 0;
  list-style: none;
}
.migas-lista > li { display: flex; align-items: baseline; column-gap: var(--mi-sep); min-inline-size: 0; }
.migas-sep { color: var(--texto-3); }
.migas-enlace {
  position: relative;
  color: var(--texto-2);
  text-decoration-line: underline;
  text-decoration-color: transparent;
  text-decoration-thickness: var(--trazo);
  text-underline-offset: var(--e-4);
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui), text-decoration-color var(--m-t2) var(--m-ui);
}
.migas-actual { min-inline-size: 0; color: var(--texto); }
/* «Volver a» y su chevron: solo en S */
.migas-volver { display: none; }
.migas-enlace > .i { margin-inline-start: calc(-1 * var(--mi-sangria, 6px)); }

/* Encima y pulsado: texto y subrayado. .es-encima y .es-pulsado, quietos en la documentación */
@media (hover: hover) {
  .migas-enlace:hover { color: var(--texto); text-decoration-color: currentColor; }
}
.migas-enlace.es-encima,
.migas-enlace:active,
.migas-enlace.es-pulsado { color: var(--texto); text-decoration-color: currentColor; }

/* En S, solo el padre: «‹ Volver a Proyectos» */
@container pagina (max-width: 639px) {
  .migas-lista > li:not(:nth-last-child(2)) { display: none; }
  .migas-lista > li:nth-last-child(2) > .migas-sep { display: none; }
  .migas-volver { display: inline; }
}

/* ---- Paginación ---- */
.paginacion {
  --pg-casilla: 44px;
  --pg-aire: var(--e-12);
  --pg-hueco: var(--e-4);
  --pg-sangria: 6px; /* aire del chevron de 16 hasta su punta, como en las migas */
  --pg-filete: calc(var(--pg-casilla) - 2 * var(--e-12)); /* 20 */
  /* Las partes leen estos tokens con su valor de respaldo: también valen sueltas */
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  min-inline-size: 0;
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.paginacion-lista { display: flex; margin: 0; padding: 0; list-style: none; }
.paginacion-lista > li { display: flex; }

/* Cada hueco, una casilla de 44 × 44 con lo suyo en el centro */
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente, .paginacion-salto) {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  block-size: var(--pg-casilla, 44px);
  min-inline-size: var(--pg-casilla, 44px);
  white-space: nowrap;
}
/* Lo que se pulsa: texto 2 y el filete, sin ancho hasta que toca (actual y pulsado) */
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente) {
  --pg-color-filete: var(--accion);
  --pg-ancho-filete: var(--pg-filete, 20px);
  position: relative;
  border-radius: 0;
  background: linear-gradient(var(--pg-color-filete), var(--pg-color-filete)) 50% 100% / 0 var(--trazo) no-repeat;
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
}
.paginacion-numero { padding-inline: var(--e-8); }
.paginacion-salto { color: var(--texto-3); }
.paginacion-anterior, .paginacion-siguiente {
  --pg-ancho-filete: calc(100% - 2 * var(--pg-aire, 12px));
  gap: var(--pg-hueco, 4px);
  padding-inline: var(--pg-aire, 12px);
}
.paginacion-anterior { margin-inline-start: calc(-1 * (var(--pg-aire, 12px) + var(--pg-sangria, 6px))); }
/* El estado: en M y L, solo para el lector (los números ya lo dicen a la vista) */
.paginacion-estado {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Actual: texto 500 y el filete de acción al pie de la casilla */
.paginacion-numero[aria-current]:not([aria-current="false"]) {
  color: var(--texto);
  font-weight: var(--p-medio);
  background-size: var(--pg-ancho-filete) var(--trazo);
}

/* Encima: texto. Pulsado: además, el filete en gris de control */
@media (hover: hover) {
  :is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente):hover { color: var(--texto); }
}
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente).es-encima { color: var(--texto); }
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente):not([aria-current]:not([aria-current="false"])):active,
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente).es-pulsado:not([aria-current]:not([aria-current="false"])) {
  --pg-color-filete: var(--linea-control);
  color: var(--texto);
  background-size: var(--pg-ancho-filete) var(--trazo);
}

/* Foco: el anillo común, por dentro de la casilla */
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente):focus-visible,
:is(.paginacion-numero, .paginacion-anterior, .paginacion-siguiente).es-foco {
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: var(--r);
}

/* En S: el estado a la vista entre los dos chevrons; los números y las palabras, fuera */
@container pagina (max-width: 639px) {
  .paginacion > .paginacion-lista { display: none; }
  .paginacion-texto { display: none; }
  .paginacion-anterior, .paginacion-siguiente { inline-size: var(--pg-casilla, 44px); padding-inline: 0; }
  .paginacion-anterior { margin-inline-start: calc(-1 * ((var(--pg-casilla, 44px) - 16px) / 2 + var(--pg-sangria, 6px))); }
  .paginacion-estado {
    position: static;
    inline-size: auto;
    block-size: auto;
    overflow: visible;
    clip-path: none;
    color: var(--texto);
    white-space: nowrap;
  }
}

/* ---- Cargar más: el botón secundario y el recuento ---- */
.paginacion--mas { gap: var(--e-16) var(--e-24); }
.paginacion-recuento {
  margin: 0;
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}

/* ---- Entre artículos: dos mitades con el título del destino ---- */
.paginacion--cadena {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: stretch;
  column-gap: var(--medianil);
  color: var(--texto);
  letter-spacing: normal;
  font-variant-numeric: normal;
}
@container pagina (min-width: 640px) {
  .paginacion--cadena { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .paginacion--cadena > .paginacion-destino[rel~="next"] { grid-column: 2; }
}
.paginacion-destino {
  display: grid;
  gap: var(--e-8);
  align-content: start;
  min-inline-size: 0;
  padding-block: var(--e-24) var(--e-32);
  border-top: var(--trazo) solid var(--linea-2);
  color: var(--texto);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--m-t2) var(--m-ui);
}
.paginacion-destino-rotulo {
  color: var(--texto-2);
  font-size: var(--t-rotulo);
  line-height: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-feature-settings: "case";
}
.paginacion-destino-titulo {
  font-size: var(--t-titulo-3);
  line-height: 1.2;
  line-height: round(nearest, calc(1em * 1.2), 4px);
  letter-spacing: -0.02em;
  text-wrap: balance;
  transition: color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .paginacion-destino:hover { border-top-color: var(--accion); }
  .paginacion-destino:hover > .paginacion-destino-titulo { color: var(--enlace); }
}
.paginacion-destino:is(.es-encima, .es-pulsado, :active) { border-top-color: var(--accion); }
.paginacion-destino:is(.es-encima, .es-pulsado, :active) > .paginacion-destino-titulo { color: var(--enlace); }

/* De derecha a izquierda, los chevrons miran al otro lado */
:dir(rtl) :is(.migas-enlace, .paginacion-anterior, .paginacion-siguiente) > .i { scale: -1 1; }

/* Con el dedo: los enlaces de las migas responden en 44 de alto (lo demás ya mide 44 o más) */
@media (any-pointer: coarse) {
  .migas-enlace::after {
    content: "";
    position: absolute;
    inset: calc((20px - 44px) / 2) calc(-1 * var(--mi-sep, 8px));
  }
}

/* Movimiento reducido: el color cambia al instante */
@media (prefers-reduced-motion: reduce) {
  .migas-enlace, .paginacion-numero, .paginacion-anterior, .paginacion-siguiente,
  .paginacion-destino, .paginacion-destino-titulo { transition: none; }
}
/* Colores forzados: el filete de fondo no se pinta; la actual, con 2 px en el color del sistema */
@media (forced-colors: active) {
  .migas-sep, .paginacion-salto { color: CanvasText; }
  .paginacion-numero[aria-current]:not([aria-current="false"]) { border-block-end: var(--trazo-foco) solid Highlight; }
  .paginacion-destino { border-top-color: CanvasText; }
}

/* ==== componentes/listas.css ==== */
/* ==========================================================================
   Lista de filas: cosas del mismo tipo, una debajo de otra, que se leen de izquierda
   a derecha: lo que las identifica delante, el nombre y lo que las distingue en el
   centro, y un dato, un estado o un control detrás.
     <ul class="lista-filas lista-filas--dos" aria-label="Proyectos">
       <li>
         <a class="lista-filas-fila" href="/proyectos/havet">
           <span class="lista-filas-delante"><img class="lista-filas-miniatura" src="…" alt="" width="40" height="40"></span>
           <span class="lista-filas-texto">
             <span class="lista-filas-titulo">Cliente</span>
             <span class="lista-filas-apoyo">Branding · 2024</span></span>
           <span class="lista-filas-detras"><svg class="i i-20 lista-filas-chevron" aria-hidden="true">…</svg></span>
         </a>
       </li>
     </ul>
   · La fila (.lista-filas-fila) es una rejilla de tres columnas: delante (auto), texto
     (lo que sobra) y detrás (auto). Cada parte lleva su hueco (16) hacia el texto: si
     falta, no queda un hueco vacío.
   · Una zona por fila. Estática: el li es la fila (li.lista-filas-fila) y no responde.
     Entera: la fila es un a o un button dentro del li; encima, fondo de capa y el chevron
     avanza 3; pulsada, un paso más (--linea). Con un control (interruptor, casilla, botón
     de opción): la fila es su label y cambiarlo es tocar cualquier parte de ella.
   · Alto mínimo por líneas, con 8 de aire como mínimo arriba y abajo y el contenido en el
     centro: una línea, 48 (.lista-filas); dos, 64 (.lista-filas--dos); tres, 80
     (.lista-filas--tres; todo sube a la primera línea). Si el texto no cabe,
     la fila crece: nada se corta.
   · Filetes de 1 px en --linea entre filas, arriba y abajo; .lista-filas--aire los quita
     (la fila encima o elegida lleva entonces radio 4).
   Delante (.lista-filas-delante): un icono (20 con una línea; 24 con dos o tres, en
   --texto-2), una miniatura 1:1 de 40 con radio 4 (.lista-filas-miniatura) o unas
   iniciales en --capa (.lista-filas-iniciales). La miniatura y las iniciales, desde dos
   líneas. Texto: título (.lista-filas-titulo, --t-cuerpo en --texto) y apoyo
   (.lista-filas-apoyo, --t-menor en --texto-2, una o dos líneas).
   Detrás (.lista-filas-detras): un dato en cifras tabulares (.lista-filas-dato), un
   estado (.etiqueta.lista-filas-estado con data-estado: icono y palabra en los roles de
   estado), los dos uno encima del otro (.lista-filas-pila), el chevron que lleva a otra
   vista (.lista-filas-chevron: el i-chevron del sprite, que mira abajo, girado a la
   derecha), un interruptor, una casilla o el botón de «Más acciones» (.lista-filas-boton,
   40 × 40 sin contorno: un contorno en cada fila pesaría más que la fila; sale 10 hacia
   el borde para que su icono caiga en la vertical del chevron y ocupa 32 de alto en la
   fila, así cabe en una de 48).
   Selección (.lista-filas-seleccion): la casilla delante, en una ranura de 40
   (.lista-filas-marca) a 8 del borde, la del botón de la barra de herramientas; el texto
   empieza a 60, donde la cuenta de la barra. Encima, la cabecera (.lista-filas-cabecera,
   56 de alto): «Elegir todas» en la misma ranura, el nombre y el número de filas, y,
   mientras hay algo elegido, la cuenta, el tinte de selección y las acciones en una
   barra de selección (css/componentes/barras-herramientas.css). La fila elegida, en
   --seleccion. js/componentes/listas.js lleva la cuenta.
   Elección: una entre muchas, con flechas. La fila es la etiqueta de un botón de opción
   nativo (.lista-filas-control, invisible sobre la fila) y la elegida lleva el tinte de
   selección y un visto detrás (.lista-filas-senal, en --accion). El grupo va en un
   fieldset (.lista-filas-grupo) con su legend (.lista-filas-rotulo).
   Grupos: cada lista con su rótulo encima (.lista-filas-rotulo, en --t-rotulo), que la
   nombra con aria-labelledby. .lista-filas-vacia: la línea que dice por qué no hay filas.
   Foco: el anillo común, por dentro de la fila (−2), sin radio: la fila va de filete a
   filete y un anillo por fuera pisaría a las vecinas. En las filas con control, en la
   casilla o en la pista; en la elección, en la fila.
   Ningún HEX: solo roles. Funciona en claro, en oscuro, sobre la banda verde y en las islas.
   ========================================================================== */
/* Los tokens, en todo lo que puede ir suelto: la lista, el bloque de la selección, su
   cabecera, el grupo y el rótulo */
.lista-filas,
.lista-filas-seleccion,
.lista-filas-cabecera,
.lista-filas-grupo,
.lista-filas-rotulo {
  --lf-alto: 48px;                /* alto mínimo de la fila: una línea */
  --lf-lado: var(--e-16);         /* relleno a los lados */
  --lf-aire: var(--e-8);          /* aire mínimo arriba y abajo */
  --lf-hueco: var(--e-16);        /* entre delante, texto y detrás */
  --lf-icono: 20px;               /* el icono de delante: 20 con una línea */
  --lf-cosa: 40px;                /* miniatura e iniciales */
  --lf-ranura: 40px;              /* la ranura de la casilla: el botón de la barra */
  --lf-boton: 40px;               /* «Más acciones» */
}
.lista-filas--dos { --lf-alto: 64px; --lf-icono: 24px; }
.lista-filas--tres { --lf-alto: 80px; --lf-icono: 24px; }

/* ---- La lista: filetes de 1 px entre filas, arriba y abajo ---- */
.lista-filas {
  margin: 0;
  padding: 0;
  list-style: none;
  border-block-start: var(--trazo) solid var(--linea);
  color: var(--texto);
}
.lista-filas > li { min-inline-size: 0; border-block-end: var(--trazo) solid var(--linea); }
.lista-filas--aire,
.lista-filas--aire > li { border: 0; }

/* ---- La fila ---- */
.lista-filas-fila {
  position: relative;
  display: grid;
  grid-template-columns: [delante] auto [texto] minmax(0, 1fr) [detras] auto;
  align-items: center;
  box-sizing: border-box;
  inline-size: 100%;
  min-block-size: var(--lf-alto);
  margin: 0;
  padding: var(--lf-aire) var(--lf-lado);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font: inherit;
  letter-spacing: inherit;
  text-align: start;
  text-decoration: none;
}
.lista-filas--aire .lista-filas-fila { border-radius: var(--r); }

/* Delante: icono, miniatura o iniciales; su hueco, hacia el texto */
.lista-filas-delante {
  grid-column: delante;
  grid-row: 1;
  display: grid;
  place-items: center;
  margin-inline-end: var(--lf-hueco);
  color: var(--texto-2);
}
.lista-filas-delante > .i { inline-size: var(--lf-icono); block-size: var(--lf-icono); }
.lista-filas-miniatura {
  display: block;
  inline-size: var(--lf-cosa);
  block-size: var(--lf-cosa);
  border-radius: var(--r);
  object-fit: cover;
  background: var(--capa);
}
.lista-filas-iniciales {
  display: grid;
  place-items: center;
  inline-size: var(--lf-cosa);
  block-size: var(--lf-cosa);
  border-radius: var(--r);
  background: var(--capa);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: 0;
  transition: background-color var(--m-t2) var(--m-ui);
}

/* Texto: título y apoyo */
.lista-filas-texto { grid-column: texto; grid-row: 1; display: grid; min-inline-size: 0; }
.lista-filas-titulo {
  font-size: var(--t-cuerpo);
  line-height: 24px;
  letter-spacing: -0.01em;
  font-weight: var(--p-normal);
  color: var(--texto);
  overflow-wrap: anywhere;
}
.lista-filas-apoyo {
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  color: var(--texto-2);
  overflow-wrap: anywhere;
}
.lista-filas-apoyo .t-mono { color: inherit; }

/* Detrás: dato, estado, chevron o control; su hueco, hacia el texto */
.lista-filas-detras {
  grid-column: detras;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--e-12);
  margin-inline-start: var(--lf-hueco);
  color: var(--texto-2);
}
.lista-filas-dato {
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  color: var(--texto);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  text-align: end;
}
/* El dato y el estado, uno encima del otro, a la derecha, en las dos líneas del texto: el
   dato en una línea de 24, como el título, y el estado (24) en la de 20 del apoyo, 2 por
   encima y 2 por debajo. Así ocupan 44, como el texto, y las bases casan con las suyas */
.lista-filas-pila { display: grid; justify-items: end; }
.lista-filas-pila > .lista-filas-dato { line-height: 24px; }
.lista-filas-pila > .lista-filas-estado { margin-block: calc(-1 * var(--e-4) / 2); }
.lista-filas-chevron { color: var(--texto-2); rotate: -90deg; }
/* El interruptor, solo su pista (sin la columna de su texto) */
.lista-filas-detras .interruptor { grid-template-columns: var(--in-ancho); gap: 0; }

/* El estado: la etiqueta con icono y palabra, en los roles de estado */
.lista-filas-estado {
  --lf-estado-icono: currentColor;
  gap: var(--e-4);
  padding-inline: var(--e-4) var(--e-8);
}
.lista-filas-estado > .i { inline-size: 16px; block-size: 16px; color: var(--lf-estado-icono); }
.lista-filas-estado[data-estado="exito"] { background: var(--exito-fondo); color: var(--exito-texto); --lf-estado-icono: var(--exito-icono); }
.lista-filas-estado[data-estado="aviso"] { background: var(--aviso-fondo); color: var(--aviso-texto); --lf-estado-icono: var(--aviso-icono); }
.lista-filas-estado[data-estado="error"] { background: var(--error-fondo); color: var(--error-texto); --lf-estado-icono: var(--error-icono); }
.lista-filas-estado[data-estado="info"] { background: var(--info-fondo); color: var(--info-texto); --lf-estado-icono: var(--info-icono); }

/* «Más acciones»: 40 × 40, sin contorno; encima y abierto, fondo de capa. Sale 10 hacia el
   borde (el relleno de la fila baja de 16 a 6) y ocupa 32 de alto en la fila */
.lista-filas-boton {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--lf-boton);
  block-size: var(--lf-boton);
  margin: calc(-1 * var(--e-4)) 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--texto);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
.lista-filas-boton .i { inline-size: 20px; block-size: 20px; transition: translate var(--m-t1) var(--m-ui); }
.lista-filas-fila:has(> .lista-filas-detras .lista-filas-boton) {
  padding-inline-end: calc(var(--lf-lado) - (var(--lf-boton) - 20px) / 2);
}
@media (hover: hover) {
  .lista-filas-boton:hover { background: var(--capa); }
}
.lista-filas-boton.es-encima,
.lista-filas-boton[aria-expanded="true"] { background: var(--capa); }
.lista-filas-boton:active,
.lista-filas-boton.es-pulsado { background: var(--capa); }
.lista-filas-boton:active .i,
.lista-filas-boton.es-pulsado .i { translate: 0 1px; }

/* ---- Tres líneas: todo arriba; delante y detrás, a la altura de la primera línea ---- */
.lista-filas--tres .lista-filas-fila > :is(.lista-filas-delante, .lista-filas-marca, .lista-filas-texto, .lista-filas-detras) { align-self: start; }
.lista-filas--tres .lista-filas-detras { min-block-size: 24px; }

/* ---- La fila entera: un enlace o un botón. Una etiqueta (la fila de un control) también
   responde entera ---- */
.lista-filas-fila:is(a, button, label) {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .lista-filas-fila:is(a, button, label):hover { background: var(--capa); }
  .lista-filas-fila:is(a, button, label):hover .lista-filas-chevron { translate: var(--lf-avance, 3px) 0; }
  .lista-filas-fila:is(a, button, label):hover .lista-filas-iniciales { background: var(--fondo); }
}
.lista-filas-fila.es-encima { background: var(--capa); }
.lista-filas-fila.es-encima .lista-filas-chevron { translate: var(--lf-avance, 3px) 0; }
.lista-filas-fila.es-encima .lista-filas-iniciales { background: var(--fondo); }
/* Pulsada: un paso más, el de la línea. El texto 2 no llega a 4,5:1 sobre ella: el
   apoyo, el icono y el chevron pasan al color del texto mientras se pulsa */
.lista-filas-fila:is(a, button, label):active,
.lista-filas-fila.es-pulsado { background: var(--linea); }
.lista-filas-fila:is(a, button, label):active :is(.lista-filas-apoyo, .lista-filas-delante, .lista-filas-chevron),
.lista-filas-fila.es-pulsado :is(.lista-filas-apoyo, .lista-filas-delante, .lista-filas-chevron) { color: var(--texto); }
.lista-filas-fila:is(a, button, label):active .lista-filas-iniciales,
.lista-filas-fila.es-pulsado .lista-filas-iniciales { background: var(--fondo); }
.lista-filas-fila:is(a, button, label):active .lista-filas-chevron,
.lista-filas-fila.es-pulsado .lista-filas-chevron { translate: var(--lf-avance, 3px) 0; }

/* Foco: el anillo común por dentro de la fila y el fondo de encima (solo con teclado) */
.lista-filas-fila:is(a, button):focus { outline: none; }
.lista-filas-fila:is(a, button):focus-visible,
.lista-filas-fila:has(> .lista-filas-control:focus-visible),
.lista-filas-fila.es-foco {
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: 0;
  background: var(--capa);
}
.lista-filas--aire .lista-filas-fila:is(:focus-visible, .es-foco),
.lista-filas--aire .lista-filas-fila:has(> .lista-filas-control:focus-visible) { border-radius: var(--r); }
.lista-filas-fila:is(a, button):focus-visible .lista-filas-iniciales,
.lista-filas-fila.es-foco .lista-filas-iniciales { background: var(--fondo); }

/* ---- Con control: la casilla y el interruptor responden a toda la fila. Encima, lo que
   harían con el puntero sobre ellos (interruptor.css, casillas.css) ---- */
@media (hover: hover) {
  .lista-filas-fila:hover .casilla-control:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--texto); }
  .lista-filas-fila:hover .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion-hover); border-color: var(--accion-hover); }
  .lista-filas-fila:hover .interruptor-control:not(:checked, [aria-disabled="true"]) + .interruptor-pista { border-color: var(--texto); }
  .lista-filas-fila:hover .interruptor-control:not(:checked, [aria-disabled="true"]) + .interruptor-pista::before { background: var(--texto); }
  .lista-filas-fila:hover .interruptor-control:checked:not([aria-disabled="true"]) + .interruptor-pista { background: var(--accion-hover); border-color: var(--accion-hover); }
}
.lista-filas-fila.es-encima .casilla-control:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--texto); }
.lista-filas-fila.es-encima .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion-hover); border-color: var(--accion-hover); }
.lista-filas-fila.es-encima .interruptor-control:not(:checked) + .interruptor-pista { border-color: var(--texto); }
.lista-filas-fila.es-encima .interruptor-control:not(:checked) + .interruptor-pista::before { background: var(--texto); }
.lista-filas-fila.es-encima .interruptor-control:checked + .interruptor-pista { background: var(--accion-hover); border-color: var(--accion-hover); }

/* ---- Selección: la casilla delante, en la ranura del botón de la barra ---- */
.lista-filas-marca {
  grid-column: delante;
  grid-row: 1;
  position: relative;
  display: grid;
  place-items: center;
  inline-size: var(--lf-ranura);
  margin-inline-end: var(--e-12);
}
/* El control invisible, sobre la caja (la fila entera es su etiqueta) */
.lista-filas-marca > .casilla-control { inset: 0; margin: auto; }
.lista-filas-fila:has(> .lista-filas-marca) { padding-inline-start: var(--e-8); }
/* Elegida: el tinte de selección (encima, también) */
.lista-filas-fila:has(> .lista-filas-marca > .casilla-control:checked),
.lista-filas-fila:has(> .lista-filas-control:checked) { background: var(--seleccion); }

/* La cabecera: «Elegir todas», el nombre y el número de filas; con algo elegido, la
   cuenta y las acciones. 56 de alto: la medida de la barra de herramientas */
.lista-filas-cabecera {
  display: flex;
  align-items: center;
  min-block-size: calc(2 * var(--e-8) + var(--lf-ranura));
  /* A la derecha, el botón de la barra sale 10 hacia el borde, como «Más acciones» */
  padding-inline: var(--e-8) calc(var(--lf-lado) - (var(--lf-boton) - 20px) / 2);
  border-block-end: var(--trazo) solid var(--linea);
  background: var(--fondo);
  color: var(--texto);
  transition: background-color var(--m-t2) var(--m-ui), border-color var(--m-t2) var(--m-ui);
}
.lista-filas-cabecera > .lista-filas-marca { flex: none; margin-inline-end: 0; }
.lista-filas-cabecera-titulo {
  flex: 0 1 auto;
  min-inline-size: 0;
  margin: 0;
  padding-inline: var(--e-12) var(--e-8);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  font-weight: var(--p-medio);
  font-variant-numeric: tabular-nums;
}
.lista-filas-cuenta { margin-inline-start: var(--e-4); font-weight: var(--p-normal); color: var(--texto-2); }
/* La barra de selección, dentro: solo sus acciones, al final; el fondo y el filete, de la
   cabecera. Ocupa lo que sobra: así sus globos salen hacia dentro, alineados por el final */
.lista-filas-cabecera > .barra-herramientas {
  flex: 1 1 auto;
  justify-content: flex-end;
  min-inline-size: 0;
  padding: var(--e-8) 0;
  border: 0;
  background: transparent;
}
.lista-filas-cabecera:has(> .barra-herramientas:not([hidden])) {
  background: var(--seleccion);
  border-block-end-color: var(--seleccion);
}
.lista-filas-cabecera + .lista-filas { border-block-start: 0; }

/* ---- Elección: la fila es la etiqueta de un botón de opción; el control, invisible,
   ocupa la fila. La elegida, en el tinte de selección y con el visto ---- */
.lista-filas-control {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: inherit;
}
.lista-filas-fila .lista-filas-detras .i.lista-filas-senal { visibility: hidden; color: var(--accion); }
.lista-filas-fila:has(> .lista-filas-control:checked) .lista-filas-detras .i.lista-filas-senal { visibility: visible; }

/* ---- Grupos: el rótulo encima de su lista; en un fieldset, su legend ---- */
.lista-filas-grupo { min-inline-size: 0; margin: 0; padding: 0; border: 0; }
.lista-filas-grupo + .lista-filas-grupo { margin-block-start: var(--e-24); }
.lista-filas-grupo > .lista-filas-rotulo { padding-block-start: 0; }
.lista-filas-rotulo {
  display: block;
  margin: 0;
  padding: var(--e-24) var(--lf-lado) var(--e-8);
  color: var(--texto-2);
  font-size: var(--t-rotulo);
  line-height: 16px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-feature-settings: "case";
  font-weight: var(--p-normal);
}
.lista-filas-rotulo:first-child { padding-block-start: 0; }
.lista-filas-rotulo:is(legend) { float: none; inline-size: 100%; }

/* Sin filas: una línea que dice por qué y qué hacer */
.lista-filas-vacia {
  display: flex;
  align-items: center;
  min-block-size: var(--lf-alto);
  padding: var(--lf-aire) var(--lf-lado);
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
}

/* ---- En S, el dato y el estado bajan bajo el texto, en una línea: a la derecha no
   dejarían sitio al título. Los controles se quedan al final ---- */
@container pagina (max-width: 639px) {
  .lista-filas-fila:has(> .lista-filas-detras > .lista-filas-pila) > .lista-filas-detras {
    grid-column: texto;
    grid-row: 2;
    justify-content: flex-start;
    margin-inline-start: 0;
    margin-block-start: var(--e-8);
  }
  .lista-filas-fila:has(> .lista-filas-detras > .lista-filas-pila) > :is(.lista-filas-delante, .lista-filas-marca) {
    grid-row: 1 / span 2;
    align-self: start;
  }
  .lista-filas-fila:has(> .lista-filas-detras > .lista-filas-pila) > .lista-filas-marca { margin-block-start: 2px; }
  .lista-filas-pila { grid-auto-flow: column; align-items: center; justify-items: start; gap: var(--e-12); }
}

/* Con el dedo: las filas ya miden 48 o más; el botón responde en 44 sin crecer */
@media (any-pointer: coarse) {
  .lista-filas-boton::before { content: ""; position: absolute; inset: -2px; }
}

/* Movimiento: el chevron avanza con la curva de respuesta (solo con las llaves) */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .lista-filas-chevron { transition: translate var(--m-t3) var(--m-ui); }
}
@media (prefers-reduced-motion: reduce) {
  .lista-filas-fila, .lista-filas-cabecera, .lista-filas-boton, .lista-filas-boton .i, .lista-filas-iniciales { transition: none; }
}
.lista-filas-chevron:dir(rtl) { rotate: 90deg; }
.lista-filas-fila:dir(rtl) { --lf-avance: -3px; }

@media (forced-colors: active) {
  .lista-filas-fila:is(a, button, label):hover,
  .lista-filas-fila:has(> .lista-filas-marca > .casilla-control:checked),
  .lista-filas-fila:has(> .lista-filas-control:checked) { outline: var(--trazo) solid Highlight; outline-offset: calc(-1 * var(--trazo)); }
  .lista-filas-estado, .lista-filas-iniciales { border: var(--trazo) solid CanvasText; }
  .lista-filas-fila .lista-filas-detras .i.lista-filas-senal { color: CanvasText; }
}

/* ==== componentes/tablas.css ==== */
/* ==========================================================================
   Tabla: datos en filas y columnas, para buscar un valor y compararlo con los demás.
   La base es la de formas.css (.tabla): filetes horizontales, rótulo de 12 en texto 2 en la
   cabecera, cifras tabulares y .num a la derecha. Este fichero no la cambia: le añade la
   leyenda a la vista, la tabla de datos y las piezas que la rodean.
     <div class="tabla-conjunto" data-tabla-por-pagina="6">
       <div class="tabla-barra">
         <p class="tabla-leyenda" aria-hidden="true"><span class="tabla-titulo">Facturas de
           2026</span><span class="tabla-fuente">Datos de ejemplo</span></p>
         <div class="tabla-seleccion" hidden>
           <p class="tabla-cuenta" data-uno="{n} factura elegida" data-varios="{n} facturas elegidas"></p>
           <div class="tabla-acciones">…botones S secundarios, con .tabla-accion-texto…</div></div>
       </div>
       <div class="tabla-desborde desborde">
         <table class="tabla tabla--datos">
           <caption class="solo-lector">Facturas de 2026</caption>
           <thead><tr>
             <th scope="col" class="tabla-casilla">…casilla «Todos» (casillas.css)…</th>
             <th scope="col">Número</th>
             <th scope="col" class="num" aria-sort="descending"><button type="button" class="tabla-ordenar">
               Importe<svg class="i i-16 tabla-orden-sin"><use href="#i-ordenar"/></svg>
               <svg class="i i-16 tabla-orden-con"><use href="#i-chevron"/></svg></button></th>
           </tr></thead>
           <tbody><tr><td class="tabla-casilla">…</td><th scope="row">F-2026-042</th>
             <td class="num" data-valor="5000">5.000,00&nbsp;€</td></tr></tbody>
           <tfoot><tr><td class="tabla-casilla"></td><th scope="row"><span class="tabla-total">
             <svg class="i i-16"><use href="#i-sumatorio"/></svg>Total</span></th>
             <td class="num">60.500,00&nbsp;€</td></tr></tfoot>
         </table>
       </div>
       <div class="tabla-pie"><p class="tabla-paginas-texto" data-plantilla="{desde}–{hasta} de {total} facturas">12 facturas</p>
         <div class="tabla-paginas" hidden>…anterior y siguiente (data-tabla-pagina)…</div></div>
     </div>
   · De lectura (.tabla, la base): la de un texto o un documento. Toma la letra del texto y
     sus filas crecen con él. La leyenda, a la vista: caption.tabla-leyenda con .tabla-titulo y
     .tabla-fuente («datos de ejemplo» una sola vez, arriba); en S, la fuente baja a su línea.
   · De datos (.tabla--datos): listados de trabajo. Texto de 15/20 que no se parte; filas de
     48 (con su filete) y el texto centrado en ellas; 24 entre columnas; 16 a los lados de la
     primera y la última, para que el fondo de la fila elegida no toque la letra.
     .tabla--compacta, filas de 40 (16 entre columnas); .tabla--amplia, filas de 56.
   · .tabla-conjunto: la tabla de datos con su barra (el título o, con filas elegidas, la
     barra de selección en su mismo sitio: nada salta) y su pie (paginación, con botones S
     cuadrados de 40). El título de la barra lleva aria-hidden: el lector lo oye en la caption,
     que va con .solo-lector y el mismo texto; la caption no puede salir de la tabla y, a la
     vista, se iría con las columnas al deslizar.
   · Ordenar: botón .tabla-ordenar dentro del th; aria-sort en el th de la columna que manda
     (una cada vez). Sin ordenar, i-ordenar en texto 3; ordenada, el rótulo en texto y el
     chevron del sentido (arriba, ascendente; abajo, descendente). En una columna .num el
     icono va delante, para que el rótulo acabe donde acaban las cifras.
   · Elegir: la primera celda de cada fila, .tabla-casilla, lleva una casilla sin texto
     (aria-label «Elegir F-2026-042»); la de la cabecera, «Todos» (data-casilla-todo). La fila
     marcada toma el fondo de selección de borde a borde.
   · Deslizar: dentro de .tabla-desborde, la casilla y la primera columna se quedan (sticky);
     al deslizar, un filete de línea 2 las separa y el lado que esconde columnas se funde en
     24 px (data-mas, tablas.js). La tabla que se desliza recibe el foco y dice «Desliza para
     ver más» al lector.
   · Pie con totales: tfoot con .tabla-total (i-sumatorio y el rótulo); la suma, en medio.
   · Estado en la celda: .tabla-estado con data-estado="exito | aviso | error" y su icono de 16:
     la palabra lo dice, el color acompaña.
   · Vacía: una fila con td.tabla-vacio (colspan) y .tabla-vacio-contenido, que se queda a la
     vista aunque la tabla se deslice.
   Por qué:
   · El texto no se parte en la tabla de datos: una fila, una línea. Si no cabe, se desliza.
     Por eso la altura de la fila es fija (--ta-fila, contando el filete) y el aire vertical
     (--ta-aire) solo cuenta si una celda lleva dos líneas.
   · Los fondos (encima, elegida) van en las celdas: la columna que se queda tiene que tapar
     lo que pasa por debajo. En reposo, solo esas celdas llevan fondo: --ta-fondo, que un
     contexto redefine si la tabla va sobre otro color (hundido, capa).
   · border-collapse: separate (con border-spacing 0) en la de datos: con collapse, el filete
     de una celda pegada no se mueve con ella.
   · La tabla que se desliza es un contenedor (container-type): el vacío mide como mucho lo
     que se ve (100cqi), no lo que mide la tabla.
   ========================================================================== */

/* ---- Leyenda a la vista: el título y la fuente, una vez y arriba ---- */
.tabla > caption.tabla-leyenda { caption-side: top; }
.tabla-leyenda {
  margin: 0;
  padding-block-end: var(--e-12);
  text-align: start;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: -0.01em;
  color: var(--texto);
}
.tabla-titulo { font-weight: var(--p-medio); }
.tabla-fuente { color: var(--texto-2); }
/* El punto que une título y fuente no se lee */
.tabla-titulo + .tabla-fuente::before { content: "\00a0· "; content: "\00a0· " / ""; }
@container pagina (max-width: 639px) {
  .tabla-titulo + .tabla-fuente { display: block; }
  .tabla-titulo + .tabla-fuente::before { content: none; }
}

/* ==== Tabla de datos ==== */
.tabla--datos {
  --ta-fila: 48px;
  --ta-aire: var(--e-12);
  --ta-hueco: var(--e-24);
  --ta-lado: var(--e-16);
  --ta-casilla: 20px;
  /* Ancho de la columna de la casilla: lo que se desplaza la primera columna al quedarse */
  --ta-ancho-casilla: calc(var(--ta-lado) + var(--ta-casilla) + var(--ta-hueco));
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
}
.tabla--compacta { --ta-fila: 40px; --ta-aire: var(--e-8); --ta-hueco: var(--e-16); }
.tabla--amplia { --ta-fila: 56px; --ta-aire: var(--e-16); }
.tabla--datos :is(th, td) {
  block-size: var(--ta-fila);
  padding: var(--ta-aire) var(--ta-hueco) var(--ta-aire) 0;
  vertical-align: middle;
  white-space: nowrap;
  background: var(--ta-bg, transparent);
  transition: background-color var(--m-t2) var(--m-ui);
}
.tabla--datos tr > :first-child { padding-inline-start: var(--ta-lado); }
.tabla--datos tr > :last-child { padding-inline-end: var(--ta-lado); }
.tabla--datos th[scope="row"] { font-weight: var(--p-normal); }
/* La columna que se queda el ancho que sobra (el cliente, el proyecto): las demás miden lo
   que su contenido y las cifras no se separan de su rótulo */
.tabla-crece { inline-size: 100%; }

/* La casilla: una columna de 20 más su aire. Sin texto: el nombre, en aria-label */
.tabla-casilla { inline-size: var(--ta-ancho-casilla); }

/* ---- Encima y elegida: el fondo, de borde a borde, en cada celda de la fila ----
   .es-encima enseña el estado quieto en la documentación */
@media (hover: hover) {
  .tabla--datos tbody tr:not(:has(> .tabla-vacio)):hover { --ta-bg: var(--ta-encima, var(--capa)); }
}
.tabla--datos tbody tr.es-encima { --ta-bg: var(--ta-encima, var(--capa)); }
.tabla--datos tbody tr:has(> .tabla-casilla .casilla-control:checked) { --ta-bg: var(--ta-elegida, var(--seleccion)); }

/* ---- Ordenar: el rótulo es un botón; el icono dice si manda y en qué sentido ---- */
.tabla-ordenar {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: var(--e-4);
  max-inline-size: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  /* Arriba en su línea: con el icono delante (columna de cifras) la línea no baja */
  vertical-align: top;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: inherit;
  white-space: nowrap;
  border-radius: var(--r);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
}
/* La zona que responde: todo el alto de la fila (44 como poco con el dedo), sin mover nada.
   El botón mide 16 de alto (el icono y la línea del rótulo): la zona sale la mitad de lo que
   le falta hasta la fila por arriba y por abajo, y 8 a los lados */
.tabla-ordenar::after {
  content: "";
  position: absolute;
  inset: calc((16px - var(--ta-fila, 48px)) / 2) calc(-1 * var(--e-8));
}
.tabla-ordenar > .i { flex: none; color: var(--texto-3); transition: color var(--m-t2) var(--m-ui); }
.tabla-orden-con { display: none; }
.tabla th:is([aria-sort="ascending"], [aria-sort="descending"]) { color: var(--texto); }
.tabla th:is([aria-sort="ascending"], [aria-sort="descending"]) .tabla-orden-sin { display: none; }
.tabla th:is([aria-sort="ascending"], [aria-sort="descending"]) .tabla-orden-con { display: block; color: currentColor; }
.tabla th[aria-sort="ascending"] .tabla-orden-con { transform: rotate(180deg); }
/* En una columna de cifras, el icono delante: el rótulo acaba donde acaban las cifras */
.tabla th.num > .tabla-ordenar { flex-direction: row-reverse; }
@media (hover: hover) {
  .tabla-ordenar:hover, .tabla-ordenar:hover > .i { color: var(--texto); }
}
/* Encima y pulsada, lo mismo: rótulo e icono en el color del texto. .es-encima y .es-pulsado
   enseñan el estado quieto en la documentación */
.tabla-ordenar.es-encima, .tabla-ordenar.es-encima > .i,
.tabla-ordenar:active, .tabla-ordenar:active > .i,
.tabla-ordenar.es-pulsado, .tabla-ordenar.es-pulsado > .i { color: var(--texto); }

/* ---- Estado en la celda: la palabra y su icono ---- */
.tabla-estado { display: inline-flex; align-items: center; gap: var(--e-8); }
.tabla-estado > .i { color: var(--texto-2); }
.tabla-estado[data-estado="exito"] > .i { color: var(--exito-icono); }
.tabla-estado[data-estado="aviso"] > .i { color: var(--aviso-icono); }
.tabla-estado[data-estado="error"] > .i { color: var(--error-icono); }
/* Sobre la selección, el icono toma el color del texto: el de estado no llega a 3:1 en
   oscuro (2,47 el aviso); la palabra sigue diciéndolo */
.tabla--datos tbody tr:has(> .tabla-casilla .casilla-control:checked) .tabla-estado > .i { color: currentColor; }

/* ---- Pie con totales: sobre un filete de línea 2, como la cabecera ---- */
.tabla--datos tbody > tr:not([hidden]):is(:last-child, :has(+ tr[hidden])) > * { border-bottom-color: var(--linea-2); }
.tabla--datos tfoot :is(th, td) { border-bottom: 0; }
.tabla--datos tfoot td { font-weight: var(--p-medio); }
.tabla--datos tfoot th[scope="row"] {
  font-size: var(--t-rotulo);
  line-height: 16px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--texto-2);
}
.tabla-total { display: inline-flex; align-items: center; gap: var(--e-8); }

/* ---- Vacía: la cabecera se queda; una fila dice por qué y qué hacer ---- */
.tabla--datos td.tabla-vacio {
  block-size: auto;
  padding-block: var(--e-48);
  white-space: normal;
  vertical-align: top;
}
.tabla-vacio-contenido {
  position: sticky;
  inset-inline-start: var(--ta-lado, 0px);
  display: grid;
  justify-items: start;
  gap: var(--e-4);
  max-inline-size: min(32em, calc(100cqi - 2 * var(--ta-lado, 0px)));
}
.tabla-vacio-contenido > * { margin: 0; }
.tabla-vacio-titulo { font-weight: var(--p-medio); }
.tabla-vacio-texto { font-size: var(--t-nota); line-height: 20px; color: var(--texto-2); }
.tabla-vacio-contenido > .boton { margin-block-start: var(--e-12); }

/* ==== Deslizar: la tabla que no cabe ==== */
.tabla-desborde {
  overflow-x: auto;
  overscroll-behavior-x: contain;
  max-inline-size: 100%;
  min-inline-size: 0;
  container-type: inline-size;
}
/* La casilla y la primera columna se quedan; tapan lo que pasa por debajo */
.tabla-desborde .tabla--datos tr > :is(:first-child, .tabla-casilla + *) {
  position: sticky;
  inset-inline-start: 0;
  z-index: 1;
  background: var(--ta-bg, var(--ta-fondo, var(--fondo)));
}
.tabla-desborde .tabla--datos tr > .tabla-casilla + * { inset-inline-start: var(--ta-ancho-casilla); }
.tabla-desborde .tabla--datos tr > td.tabla-vacio:first-child { position: static; background: none; }
/* Al deslizar, un filete separa la columna que se queda de la que pasa por debajo */
.tabla-desborde[data-mas~="izq"] .tabla--datos tr > :is(:first-child:not(.tabla-casilla), .tabla-casilla + *)::after {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-end: 0;
  inline-size: var(--trazo);
  background: var(--linea-2);
}
/* Que se desliza lo dice la nota de encima (el patrón común: armazon.js › desliza, especimen.css
   › .desliza-nota), sin fundidos ni sombras. En el conjunto, a ras del título de la barra */
.tabla-conjunto > .desliza-nota { padding-inline: var(--ta-lado); }

/* ==== Conjunto: barra, tabla y pie ==== */
.tabla-conjunto {
  --ta-lado: var(--e-16);
  display: grid;
  inline-size: 100%;
  min-inline-size: 0;
}
/* La barra: el título o, con filas elegidas, la selección, en el mismo sitio */
.tabla-barra { display: grid; align-items: center; min-block-size: 56px; }
.tabla-barra > * { grid-area: 1 / 1; min-inline-size: 0; }
.tabla-barra > .tabla-leyenda { padding: var(--e-8) var(--ta-lado); }
.tabla-barra:has(> .tabla-seleccion:not([hidden])) > .tabla-leyenda { visibility: hidden; }
.tabla-seleccion {
  /* Los bordes de control sobre la selección: neutro-500 da 2,92:1 en claro; texto 2, 4,52 */
  --linea-control: var(--texto-2);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-8) var(--e-16);
  align-self: stretch;
  padding: var(--e-8) var(--e-8) var(--e-8) var(--ta-lado);
  border-radius: var(--r);
  background: var(--ta-elegida, var(--seleccion));
  color: var(--texto);
}
.tabla-seleccion[hidden] { display: none; }
.tabla-cuenta { margin: 0; font-size: var(--t-menor); line-height: 20px; font-weight: var(--p-medio); font-variant-numeric: tabular-nums; }
.tabla-acciones { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--e-8); }
.tabla-acciones .boton { position: relative; }
/* En S, los botones de la barra se quedan en su icono (el texto sigue siendo su nombre) y
   son cuadrados: 11 a cada lado del icono de 16 con su borde de 1 hacen 40, el botón S */
@container pagina (max-width: 639px) {
  .tabla-acciones .boton { padding-inline: 11px; }
  .tabla-accion-texto {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
/* El pie: cuántas se ven de cuántas, y anterior y siguiente */
.tabla-pie {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--e-8) var(--e-16);
  min-block-size: 56px;
  padding: var(--e-8) var(--e-8) 0 var(--ta-lado);
}
.tabla-paginas-texto { margin: 0; font-size: var(--t-nota); line-height: 20px; color: var(--texto-2); font-variant-numeric: tabular-nums; }
.tabla-paginas { display: flex; gap: var(--e-8); }
.tabla-paginas[hidden] { display: none; }
.tabla-paginas .boton { inline-size: var(--alto-s); padding-inline: 0; justify-content: center; }
/* En el principio no hay anterior y en el final no hay siguiente: no se ve ni se alcanza */
.tabla-paginas [data-limite] { visibility: hidden; }

/* ---- Movimiento: la barra de selección entra como lo contextual (t3, 4 px, fundido) cuando
   aparece al elegir (data-entra, lo pone tablas.js y lo quita al acabar). Una barra que ya
   está a la vista al cargar no se mueve ---- */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .tabla-seleccion[data-entra] { animation: ta-entra var(--m-t3) var(--m-ui) backwards; }
}
@keyframes ta-entra { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }

/* ---- Con el dedo: 44 de zona sin mover el dibujo ---- */
@media (any-pointer: coarse) {
  .tabla-ordenar::after { inset: calc(min(16px - var(--ta-fila, 48px), 16px - 44px) / 2) calc(-1 * var(--e-8)); }
  .tabla-paginas .boton { inline-size: 44px; }
}
/* Con el dedo el botón S mide 44 de alto (formas.css): el cuadrado, 13 a cada lado */
@container pagina (max-width: 639px) {
  @media (any-pointer: coarse) {
    .tabla-acciones .boton { padding-inline: 13px; }
  }
}

/* Movimiento reducido: el color cambia al instante y la barra aparece sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .tabla--datos :is(th, td), .tabla-ordenar, .tabla-ordenar > .i { transition: none; }
  .tabla-seleccion { animation: none; }
}
@media (forced-colors: active) {
  .tabla-ordenar > .i { color: CanvasText; }
  .tabla-seleccion { border: var(--trazo) solid CanvasText; }
  .tabla-desborde[data-mas~="izq"] .tabla--datos tr > :is(:first-child:not(.tabla-casilla), .tabla-casilla + *)::after { background: CanvasText; }
}
@media print {
  .tabla-desborde { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .tabla-seleccion, .tabla-paginas { display: none; }
}

/* ==== componentes/separadores.css ==== */
/* ==========================================================================
   Separador: el filete de 1 px de la base (hr, .filete en formas.css) hecho pieza de uso.
   Separa dos grupos cuando el aire no basta: primero aire; el filete, solo si hace falta.
     <ul>…</ul>
     <hr class="separador separador--sangria">
     <ul>…</ul>
     <hr class="separador">
     <p>…</p>
   · A sangre (.separador): de borde a borde de lo que lo contiene. Entre grupos o temas.
   · Con sangría (.separador--sangria): empieza donde empieza el texto de una lista con
     iconos, tras el icono de 20 y su hueco de 12 (--se-sangria, 32). La columna de iconos
     queda entera: la lista sigue siendo una; solo se parte el texto. Si delante va otra
     cosa, se cambia --se-sangria en el separador: lo de delante y su hueco.
   · Reforzado (.separador--reforzado): en --linea-2, un paso más. Bajo un encabezado, como
     el filete que cierra la cabecera de una tabla; sobre capa, también el común, porque la
     línea se pierde en ella (1,14:1).
   · Fino (.separador--fino): 0,5 px, solo en pantallas de 2x (con 1x, 1 px). El de
     .filete.fino de la base.
   · Vertical (.separador--vertical): entre dos grupos de una fila que no se parte (una fila de
     controles, una cabecera). Un div con role="separator" aria-orientation="vertical". Mide lo
     que la fila; --se-recorte lo acorta lo mismo arriba y abajo (4 en una fila de chips de
     32, 8 en una de botones de 40: queda en 24, el filete de grupo de la barra de herramientas).
   · Con texto (.separador--texto): «o» entre dos caminos, en --t-nota y --texto-2, entre
     dos filetes. No se centra: el primero mide 16 (--se-guia) y el texto cae a 24 del borde,
     en la bandera, como todo; el segundo llega al final. Los filetes cruzan el texto a media
     altura de x (el centro del trazo, a .5ex de la línea de base): pasan por el centro del
     «o». Es un p: el lector lee «o» entre los dos caminos, como se ve.
   · Fila de datos (.separador-fila, en un ul o un dl): un filete vertical entre cada dos
     datos, que mide lo que la fila. La fila se parte si no cabe y el filete no queda nunca al
     principio de una línea: va en el hueco delante de cada dato (--se-hueco a cada lado) y el
     recorte del lado de inicio (clip-path, 6 px fuera de la fila) se come el del primero de
     cada línea sin cortar el anillo del foco (5 px). --se-hueco no baja de 8.
   · Punto medio (.separador-punto): « · » dentro de una línea de texto, en --texto-3 (un signo,
     no texto que haya que leer). Espacio de no separación delante y normal detrás: si la
     línea se parte, el punto se queda arriba. Con aria-hidden: el lector no lo deletrea.
   Aire: el filete no trae el suyo (el hr de la base va sin margen) ni pisa el de lo que lo
   contiene: el gap de la pila; en la prosa, una línea. Así cae en medio, con lo mismo arriba
   y abajo, y el aire se decide una vez.
   Sin nada a un lado no se pinta: al principio o al final de lo que lo contiene, o junto a
   otro separador (lo que lleva hidden no cuenta; de dos, queda el segundo). Si un filtro vacía
   un grupo, su filete se va.
   Sentido: hr (o role="separator") cuando marca un cambio de tema o separa grupos que el
   lector tiene que distinguir; aria-hidden="true" cuando solo acompaña a lo que ya separa
   (bajo un encabezado, entre datos de una lista).
   No se pulsa ni recibe el foco: no hay encima, pulsado ni foco, ni zona de 44. No se mueve:
   llega y se va con lo que separa, sin recorrido que quitar con «reducir movimiento».
   Los filetes separan y no informan: no piden 3:1 (agrupan el aire y lo que se lee a cada lado).
   ========================================================================== */
.separador {
  --se-color: var(--linea);
  --se-trazo: var(--trazo);
  --se-sangria: calc(20px + var(--e-12));
  --se-recorte: 0px;
  --se-guia: var(--e-16);
  display: block;
  justify-self: stretch;
  align-self: stretch;
  inline-size: auto;
  padding: 0;
  border: 0;
  border-block-start: var(--se-trazo) solid var(--se-color);
  overflow: visible;
}

/* Sin nada a un lado no se pinta: primero o último de lo que lo contiene (lo oculto no
   cuenta), o junto a otro separador, aunque entre los dos haya hasta tres ocultos: de los
   dos queda el segundo, el que abre lo que sigue */
.separador:not(:not([hidden], .separador, script, template) ~ *),
.separador:not(:has(~ :not([hidden], .separador, script, template))),
.separador:has(+ .separador, + [hidden] + .separador, + [hidden] + [hidden] + .separador,
  + [hidden] + [hidden] + [hidden] + .separador) { display: none; }

/* Con sangría: empieza donde el texto */
.separador--sangria { margin-inline-start: var(--se-sangria); }

/* Reforzado: un paso más */
.separador--reforzado { --se-color: var(--linea-2); }

/* Fino: 0,5 px solo donde hay píxeles para dibujarlo */
@media (min-resolution: 2dppx) {
  .separador--fino { --se-trazo: .5px; }
}

/* Vertical: mide lo que la fila, menos el recorte */
.separador--vertical {
  flex: none;
  inline-size: 0;
  block-size: auto;
  margin-block: var(--se-recorte);
  border-block-start: 0;
  border-inline-start: var(--se-trazo) solid var(--se-color);
}

/* Con texto: «o» entre dos filetes, en la bandera */
.separador--texto {
  display: flex;
  align-items: baseline;
  gap: var(--e-8);
  border-block-start: 0;
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
}
.separador--texto::before,
.separador--texto::after {
  content: "";
  block-size: calc(.5ex + var(--se-trazo) / 2);
  border-block-start: var(--se-trazo) solid var(--se-color);
}
.separador--texto::before { flex: none; inline-size: var(--se-guia); }
.separador--texto::after { flex: 1 1 0; min-inline-size: var(--se-guia); }

/* Fila de datos: el filete va en el hueco delante de cada dato; el del primero de cada línea
   cae fuera de la fila y el recorte se lo come */
.separador-fila {
  --se-color: var(--linea);
  --se-trazo: var(--trazo);
  --se-hueco: var(--e-16);
  display: flex;
  flex-wrap: wrap;
  column-gap: calc(2 * var(--se-hueco) + var(--se-trazo));
  row-gap: var(--e-8);
  clip-path: inset(-8px -8px -8px -6px);
}
/* Sin márgenes ni viñetas, sin pisar el ritmo de lo que la contiene (la prosa) */
:where(.separador-fila) { margin: 0; padding: 0; list-style: none; }
:dir(rtl).separador-fila { clip-path: inset(-8px -6px -8px -8px); }
.separador-fila > * { position: relative; min-inline-size: 0; margin: 0; }
.separador-fila > * + *::before {
  content: "";
  position: absolute;
  inset-block: 0;
  inset-inline-start: calc(-1 * (var(--se-hueco) + var(--se-trazo)));
  border-inline-start: var(--se-trazo) solid var(--se-color);
}

/* Punto medio: un signo en la línea, en texto 3 */
.separador-punto { color: var(--texto-3); }

@media (forced-colors: active) {
  .separador,
  .separador--texto::before,
  .separador--texto::after,
  .separador-fila > * + *::before { border-color: GrayText; }
  .separador-punto { color: GrayText; }
}

/* ==== componentes/dialogos.css ==== */
/* ==========================================================================
   Diálogo: una pregunta o una tarea corta que pide respuesta antes de seguir.
   Un <dialog> modal (showModal): tapa la página con el velo, la deja inerte y guarda el
   foco hasta que se responde. Sin JS ya funciona donde el navegador conoce las órdenes
   (commandfor y command): el botón lo abre, «Cerrar», «Cancelar», Escape y el formulario
   method="dialog" lo cierran.
     <button type="button" class="boton boton--secundario" commandfor="archivar" command="show-modal">Archivar</button>
     <dialog class="dialogo dialogo--estrecho" id="archivar" aria-labelledby="archivar-t" aria-describedby="archivar-d">
       <form class="dialogo-marco" method="dialog">
         <div class="dialogo-cab">
           <h2 class="dialogo-titulo" id="archivar-t" tabindex="-1">¿Archivar el proyecto del Cliente?</h2>
           <button type="button" class="boton-icono boton-icono--contorno dialogo-cerrar"
             commandfor="archivar" command="close" aria-label="Cerrar">(i-cerrar)</button>
         </div>
         <div class="dialogo-cuerpo"><p id="archivar-d">…</p></div>
         <div class="dialogo-pie">
           <button type="submit" class="boton boton--primario" value="archivar">Archivar el proyecto</button>
           <button type="button" class="boton boton--secundario" commandfor="archivar" command="close">Cancelar</button>
         </div>
       </form>
     </dialog>
   · Ancho: 560 la base (formularios cortos, informativos); .dialogo--estrecho, 400 (confirmar
     y destructivo); .dialogo--ancho, 720 (contenido largo o en columnas). Nunca más que el
     ancho de la ventana menos el margen de la página a cada lado (24 · 40 · 48); de alto,
     lo mismo: si no cabe, se desplaza el cuerpo.
   · .dialogo--pantalla: en S ocupa la pantalla entera, con la zona segura del teléfono
     (formularios: con el teclado abierto, un diálogo centrado se queda sin sitio). En M y L
     es un diálogo más.
   · Informativo: closedby="any": un clic en el velo también lo cierra (el JS lo hace donde
     el navegador aún no conoce closedby). En los demás, el velo no cierra: un clic de más no
     pierde lo escrito ni responde por nadie.
   · Destructivo: .dialogo-consecuencia en el cuerpo: el icono de alerta en rojo y la frase
     que dice lo que se pierde, enlazada desde el diálogo con aria-describedby.
   · Con pregunta al salir: data-dialogo-pregunta="id" nombra otro diálogo (estrecho) que
     pregunta si se sale sin guardar; el JS lo abre si hay cambios.
   · Tiene que vivir dentro de la página (en la herramienta, en el lienzo): de ella toma su
     margen, su tramo (@container pagina) y sus roles, también en la capa superior.
   js/componentes/dialogos.js: el foco, la página quieta, Escape, la pregunta al salir y la
   vuelta del foco al botón que lo abrió.
   Caja: fondo, filete de 1 px en línea 2 y radio 4. Sin sombra: la separa el velo (--velo).
   Relleno de 24. Cabecera: el título en titulo-3 y «Cerrar», el botón de icono de contorno
   del sistema a 40 × 40 (el alto del botón S: al lado del título, el de 48 pesa más que él),
   centrado en la primera línea del título y a 16 de él. Cuerpo: texto de cuerpo a 16 del
   título. Si no cabe, se desplaza solo él (cabecera y pie quietos) y un filete de 1 px en
   línea marca el borde por donde queda texto tapado; arriba del todo y abajo del todo no
   se ve. Pie a 24 del cuerpo: la acción principal primero y la secundaria después, a 12.
   En M y L, a la derecha; en S (y en un pie de menos de 350), una encima de otra y a todo
   el ancho, la principal arriba.
   Entra en t4 (fundido y sube 4 px) con la curva de entrada; el velo, fundido en t4. Sale
   en t3 con la curva de salida (donde el navegador lo deja en la capa superior mientras
   sale; si no, se cierra en el acto). Con «reducir movimiento», aparece y se va sin
   recorrido.
   ========================================================================== */
.dialogo {
  --di-ancho: 560px;
  --di-relleno: var(--e-24);
  --di-margen: var(--margen, var(--e-24));        /* el de la página: 24 · 40 · 48 */
  --di-cerrar: 40px;                               /* «Cerrar»: el alto del botón S */
  --di-linea: 28px;                                /* la línea del título (respaldo) */
  --di-apila: 350px;                               /* pie más estrecho: botones apilados */
  --di-zona-arriba: 0px;                           /* la zona segura, a pantalla completa */
  --di-zona-derecha: 0px;
  --di-zona-abajo: 0px;
  --di-zona-izquierda: 0px;
  position: fixed;
  inset: 0;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  inline-size: min(var(--di-ancho), 100% - 2 * var(--di-margen));
  block-size: fit-content;
  max-inline-size: none;
  max-block-size: calc(100% - 2 * var(--di-margen));
  margin: auto;
  padding: 0;
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  font-size: var(--t-cuerpo);
  line-height: 1.5;
  line-height: round(nearest, calc(1em * 1.6), 4px);
  letter-spacing: -0.01em;
  text-align: start;
  overflow: hidden;
  overscroll-behavior: contain;
}
/* La línea del título, la de titulo-3 (1,2 em redondeada a 4): 24 a 20 px, 28 a 24 px. La
   usan el título y «Cerrar», que se centra en ella */
@supports (width: round(nearest, 1px, 4px)) {
  .dialogo { --di-linea: round(nearest, calc(var(--t-titulo-3) * 1.2), 4px); }
}
/* Cerrado no se ve (la caja es flex: sin esto, el navegador no lo escondería) */
.dialogo:is(dialog):not([open]), .dialogo[hidden] { display: none; }
.dialogo--estrecho { --di-ancho: 400px; }
.dialogo--ancho { --di-ancho: 720px; }
.dialogo::backdrop { background: var(--velo); }

/* El marco (el formulario method="dialog", o un div): la columna que reparte el alto */
.dialogo-marco {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-block-size: 0;
  margin: 0;
}

/* ---- Cabecera: el título y «Cerrar» ---- */
.dialogo-cab {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  column-gap: var(--e-16);
  flex: none;
  padding: var(--di-relleno) var(--di-relleno) var(--e-16);
}
.dialogo-titulo {
  margin: 0;
  font-size: var(--t-titulo-3);
  line-height: var(--di-linea);
  letter-spacing: -0.02em;
  font-weight: var(--p-normal);
  text-wrap: balance;
  overflow-wrap: break-word;
}
/* El título recibe el foco cuando no hay campos (para que el lector empiece por él): no es
   un control y no lleva anillo */
.dialogo-titulo:focus { outline: none; }
/* «Cerrar»: el botón de icono de contorno, a 40; su centro, en el de la línea del título */
.dialogo-cerrar {
  position: relative;
  inline-size: var(--di-cerrar, 40px);
  block-size: var(--di-cerrar, 40px);
  margin-block: calc((var(--di-linea, 28px) - var(--di-cerrar, 40px)) / 2);
}
/* Encima y pulsado, los del botón de contorno: el borde en el color del texto y, al pulsar,
   baja 1 px. .es-encima y .es-pulsado los enseñan quietos en la documentación */
.dialogo-cerrar.es-encima, .dialogo-cerrar.es-pulsado { border-color: var(--texto); }
.dialogo-cerrar.es-pulsado { transform: translateY(1px); }

/* ---- Cuerpo: se desplaza él solo si no cabe ----
   El filete del borde por donde hay texto tapado sale de cuatro capas de fondo de 1 px: dos
   filetes en línea, quietos en el borde de la caja (scroll), y dos tapas del color del fondo
   que viajan con el texto (local). Arriba del todo, la tapa de arriba cubre su filete; abajo
   del todo, la de abajo cubre el suyo; si todo cabe, las dos los cubren. Las tapas miden 2 px:
   un píxel de más al redondear el alto no enseña el filete. Sin JS y sin sombras */
.dialogo-cuerpo {
  display: grid;
  align-content: start;
  gap: var(--e-16);
  flex: 1 1 auto;
  min-block-size: 0;
  padding-inline: var(--di-relleno);
  overflow-y: auto;
  overscroll-behavior: contain;
  background:
    linear-gradient(var(--fondo), var(--fondo)) top / 100% calc(2 * var(--trazo)) no-repeat local,
    linear-gradient(var(--fondo), var(--fondo)) bottom / 100% calc(2 * var(--trazo)) no-repeat local,
    linear-gradient(var(--linea), var(--linea)) top / 100% var(--trazo) no-repeat scroll,
    linear-gradient(var(--linea), var(--linea)) bottom / 100% var(--trazo) no-repeat scroll;
}
.dialogo-cuerpo > * { margin: 0; }
.dialogo-cuerpo > p { max-inline-size: 32em; }
/* Sin pie (el informativo), el cuerpo deja abajo el mismo aire que los lados */
.dialogo-cuerpo:last-child { padding-block-end: var(--di-relleno); }
/* Con campos, a 24 uno de otro: el espacio de grupo de los formularios */
.dialogo-cuerpo:has(.campo, fieldset) { gap: var(--e-24); }

/* Destructivo: lo que se pierde, con el icono de alerta en rojo delante (nunca solo color:
   la frase lo dice) */
.dialogo-consecuencia {
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr);
  gap: var(--e-12);
  align-items: start;
}
.dialogo-consecuencia .i {
  margin-block-start: 2px;
  margin-block-start: calc((1lh - 20px) / 2);
  color: var(--error-icono);
}

/* ---- Pie: la principal primero; en M y L, a la derecha ----
   Si el pie mide menos que el del estrecho (350 = 400 − 2 × 1 − 2 × 24), los botones no caben
   en una línea: van uno encima de otro y a todo el ancho, también en M y L (una caja estrecha
   dentro de una página ancha). Sin consulta de ancho: cada botón mide su texto mientras el pie
   mida 350 o más, y el pie entero cuando mide menos */
.dialogo-pie {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: var(--e-12);
  flex: none;
  padding: var(--di-relleno);
}
.dialogo-pie > .boton {
  inline-size: max(0px, (var(--di-apila) - 100%) * 999);
  min-inline-size: max-content;
  max-inline-size: 100%;
  justify-content: center;
}
/* En S, una encima de otra y a todo el ancho, la principal arriba (el orden del HTML: el
   foco las recorre como se ven) */
@container pagina (max-width: 639px) {
  .dialogo-pie { display: grid; justify-content: stretch; }
  .dialogo-pie > .boton { inline-size: auto; }
}

/* ---- A pantalla completa en S ---- */
@container pagina (max-width: 639px) {
  .dialogo--pantalla {
    --di-zona-arriba: env(safe-area-inset-top, 0px);
    --di-zona-derecha: env(safe-area-inset-right, 0px);
    --di-zona-abajo: env(safe-area-inset-bottom, 0px);
    --di-zona-izquierda: env(safe-area-inset-left, 0px);
    inline-size: 100%;
    block-size: 100%;
    max-block-size: none;
    border: 0;
    border-radius: 0;
  }
  .dialogo--pantalla .dialogo-cab {
    padding-block-start: calc(var(--di-relleno) + var(--di-zona-arriba));
    padding-inline: calc(var(--di-relleno) + var(--di-zona-izquierda)) calc(var(--di-relleno) + var(--di-zona-derecha));
  }
  .dialogo--pantalla .dialogo-cuerpo {
    padding-inline: calc(var(--di-relleno) + var(--di-zona-izquierda)) calc(var(--di-relleno) + var(--di-zona-derecha));
  }
  .dialogo--pantalla .dialogo-pie {
    padding: var(--di-relleno) calc(var(--di-relleno) + var(--di-zona-derecha)) calc(var(--di-relleno) + var(--di-zona-abajo)) calc(var(--di-relleno) + var(--di-zona-izquierda));
  }
}

/* ---- Movimiento ----
   Entra: fundido y sube 4 px en t4 con la curva de entrada; el velo, fundido en t4 (la de
   los fundidos). Sale: fundido y baja 4 px en t3 con la curva de salida, y el velo con él;
   solo donde el navegador lo deja en la capa superior mientras sale (overlay): si no, el
   diálogo saldría de ella y bajaría a la página a media salida. El estado de salida
   (opacidad 0) es el del diálogo cerrado, que no se ve: nada se queda a 0 en reposo */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .dialogo:is(dialog)[open] {
    transition: opacity var(--m-t4) var(--m-sale), translate var(--m-t4) var(--m-sale);
  }
  html[data-mov] .dialogo:is(dialog)[open]::backdrop { transition: opacity var(--m-t4) var(--m-suave); }
  @starting-style {
    html[data-mov] .dialogo:is(dialog)[open] { opacity: 0; translate: 0 var(--e-4); }
    html[data-mov] .dialogo:is(dialog)[open]::backdrop { opacity: 0; }
  }
  @supports (overlay: auto) {
    html[data-mov] .dialogo:is(dialog) {
      transition:
        opacity var(--m-t3) var(--m-va),
        translate var(--m-t3) var(--m-va),
        display var(--m-t3) var(--m-va) allow-discrete,
        overlay var(--m-t3) var(--m-va) allow-discrete;
    }
    html[data-mov] .dialogo:is(dialog):not([open]) { opacity: 0; translate: 0 var(--e-4); }
    html[data-mov] .dialogo:is(dialog)::backdrop {
      transition:
        opacity var(--m-t3) var(--m-va),
        display var(--m-t3) var(--m-va) allow-discrete,
        overlay var(--m-t3) var(--m-va) allow-discrete;
    }
    html[data-mov] .dialogo:is(dialog):not([open])::backdrop { opacity: 0; }
  }
}

/* Con el dedo, «Cerrar» responde en 44 sin crecer */
@media (any-pointer: coarse) {
  .dialogo-cerrar::after {
    content: "";
    position: absolute;
    inset: calc((var(--di-cerrar, 40px) - 44px) / 2);
  }
}

/* Movimiento reducido: aparece y se va sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .dialogo, .dialogo::backdrop { transition: none; }
}
@media (forced-colors: active) {
  .dialogo { border-color: CanvasText; }
  .dialogo-consecuencia .i { color: CanvasText; }
}

/* ==== componentes/hojas-inferiores.css ==== */
/* ==========================================================================
   Hoja inferior: contenido secundario que sube desde el borde de abajo, sobre todo en S
   (filtros de una lista, compartir, el índice de un artículo). Un <dialog>: con velo es
   modal (el resto queda inerte y Escape la cierra); sin velo, la página sigue a mano.
     <button type="button" commandfor="hoja-filtros" command="show-modal"
             aria-haspopup="dialog">Filtros</button>
     <dialog class="hoja-inferior" id="hoja-filtros" aria-labelledby="hoja-filtros-t">
       <div class="hoja-inferior-cabeza">
         <button type="button" class="hoja-inferior-asa" aria-hidden="true" tabindex="-1"></button>
         <h2 class="hoja-inferior-titulo" id="hoja-filtros-t">Filtros</h2>
         <button type="button" class="boton-icono boton-icono--contorno hoja-inferior-cerrar"
                 commandfor="hoja-filtros" command="close" aria-label="Cerrar">
           <svg class="i i-20" aria-hidden="true"><use href="#i-cerrar"/></svg></button>
       </div>
       <div class="hoja-inferior-cuerpo">… (se desplaza) …</div>
       <div class="hoja-inferior-pie">… (opcional: las acciones) …</div>
     </dialog>
   Sin JS, commandfor y command abren y cierran el diálogo del navegador (modal) y Escape
   la cierra. El JS (js/componentes/hojas-inferiores.js) pone el recorrido, el alto medio y
   el completo, el asa, el arrastre, el velo que cierra y la vuelta del foco.
   · Con velo (lo normal): modal, en la capa superior, con ::backdrop en --velo.
   · .hoja-inferior--sin-velo: no modal (show()); la página sigue viva debajo.
   · .hoja-inferior--lateral: desde 640, sale por la derecha a toda la altura (lo que se
     consulta junto a la página: filtros, un índice); sin ella, se centra como un diálogo
     (lo que se lee de una vez y se cierra: compartir).
   · .hoja-inferior-marco: un contenedor (una muestra de móvil, una vista incrustada) del
     que la hoja no sale: es su página («pagina», para el ancho), la hoja va en absolute
     dentro y el velo es .hoja-inferior-velo (lo pone el JS); lo demás del marco, inerte.
   · Un <form method="dialog"> puede envolver cabeza, cuerpo y pie (display: contents).
   · Un div.hoja-inferior se pinta siempre: la hoja quieta de la documentación.
   Estados en atributos: data-alto="medio" | "completo" (lo pone el JS), data-desplazada
   (el cuerpo se ha desplazado), data-arrastre (la lleva el dedo), data-paso="sale" |
   "vuelve" (el recorrido de salida o de vuelta a su sitio). Encima, pulsado y foco del
   asa, también quietos (.es-encima, .es-pulsado, .es-foco).
   Medidas y porqués:
   · A todo el ancho y pegada abajo. Radio 4 solo en las esquinas de arriba, las que se
     ven; filete de 1 en --linea-2 solo arriba (se afina en el radio: los lados caen en
     el borde de la pantalla). Sin sombra: la separa el filete y, si tapa la página, el
     velo. Fondo --fondo: en oscuro y sobre la banda verde cambia sola.
   · Alto máximo 90 dvh (--hi-completo) y 50 dvh a media altura (--hi-medio): el cuerpo se
     desplaza por dentro; la cabeza y el pie se quedan. Abajo, la zona segura del
     teléfono (env(safe-area-inset-bottom)): el fondo llega al borde y nada queda debajo.
   · Texto a 24 del borde (--hi-lado): el margen de S, así la hoja cae en las líneas de la
     página. La letra es de interfaz: no respira con la ventana (título 20/28).
   · Asa: 32 × 4, radio 2 (pieza de menos de 12), en --linea-control (3,64:1 en claro,
     4,79 en oscuro), en una zona de 48 × 24 centrada en el borde: es el mando del gesto,
     no texto. Es un botón que alterna el alto medio y el completo (aria-label: «Ampliar
     la hoja» o «Reducir la hoja»); si todo cabe a media altura, es solo dibujo. Encima,
     el dibujo en --texto; pulsada, además, la zona en --capa. El foco, por dentro de la
     zona (−2): la hoja recorta lo que sale por arriba.
   · Cabeza: la fila del asa (24) y la del título y Cerrar (48, el botón de icono), 8
     debajo. Con el cuerpo desplazado, un filete de 1 en --linea la separa.
   · Pie: 16 arriba y abajo, filete de 1 en --linea; la acción principal delante.
   · Desde 640 (@container pagina) el borde de abajo queda lejos del pulgar:
     se centra como un diálogo de 480 (cuatro columnas de L: 4 × 96 + 3 × 32), con radio 4
     y filete alrededor, a 48 de los bordes como poco; o, con --lateral, sale por la
     derecha a toda la altura, con 480 de ancho como mucho y filete a la izquierda. El
     asa no se ve (visibility: guarda su hueco de 24) y no hay arrastre.
   · Movimiento (receta de panel, en vertical): entra en t5 con --m-traza y sale en t3
     con --m-va; el velo se funde a la vez. El alto cambia en t4 con --m-ui; soltada a
     medio camino, vuelve en t3 con --m-ui. Centrada, sube 4 y aparece en t3. Con
     «reducir movimiento», aparece y se va sin recorrido.
   · Capas: modal, en la capa superior; sin velo o en un marco, z-index 45 (el velo, 44),
     bajo la cabecera de la herramienta (50).
   ========================================================================== */
.hoja-inferior {
  --hi-lado: var(--e-24);          /* texto a 24 del borde: el margen de S */
  --hi-medio: 50dvh;               /* a media altura */
  --hi-completo: 90dvh;            /* alto máximo */
  --hi-ancho: 480px;               /* desde 640: cuatro columnas de L */
  --hi-asa-ancho: 32px;
  --hi-asa-alto: 4px;
  --hi-asa-zona: 48px;             /* la zona del asa: 48 × 24 (44 de alto con el dedo) */
  --hi-asa-fila: 24px;
  /* Lo que lee el JS: la forma que toma con el ancho de su página */
  --hi-forma: hoja;
  /* De dónde entra y adónde sale: abajo, fuera de la vista */
  --hi-fuera: 0 100%;
  --hi-fuera-opacidad: 1;
  --hi-entra: var(--m-t5);
  --hi-entra-curva: var(--m-traza);
  /* Interfaz: cada paso de letra en su cota menor */
  --t-cuerpo: 16px;
  --t-destacado: 18px;
  --t-titulo-3: 20px;
  --t-titulo-2: 28px;

  position: fixed;
  z-index: 45;
  inset: auto 0 0;
  box-sizing: border-box;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  inline-size: 100%;
  max-inline-size: none;
  block-size: auto;
  max-block-size: var(--hi-completo);
  margin: 0;
  padding: 0 env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) env(safe-area-inset-left, 0px);
  border: 0;
  border-block-start: var(--trazo) solid var(--linea-2);
  border-radius: var(--r) var(--r) 0 0;
  background: var(--fondo);
  color: var(--texto);
  /* Recorta sin ser una caja que se desplaza: al abrir, el navegador lleva el foco dentro
     mientras la hoja aún está fuera, entrando, y movería lo que la contiene para enseñarlo */
  overflow: hidden;
  overflow: clip;
  overscroll-behavior: contain;
}
.hoja-inferior:is(dialog):not([open]) { display: none; }
.hoja-inferior[data-alto="medio"] { max-block-size: var(--hi-medio); }
.hoja-inferior::backdrop { background: var(--velo); }
.hoja-inferior > form { display: contents; }
/* Modal en la capa superior: la página de detrás no se desplaza (lo pone el JS) */
html[data-hoja-inferior] { overflow: hidden; scrollbar-gutter: stable; }

/* ---- Cabeza: el asa, el título y Cerrar. Se arrastra entera (en S) ---- */
.hoja-inferior-cabeza {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-rows: var(--hi-asa-fila) auto;
  align-items: center;
  column-gap: var(--e-16);
  padding: 0 var(--hi-lado) var(--e-8);
  border-block-end: var(--trazo) solid transparent;
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
  cursor: grab;
  transition: border-color var(--m-t2) var(--m-ui);
}
.hoja-inferior[data-arrastre] .hoja-inferior-cabeza { cursor: grabbing; }
.hoja-inferior[data-desplazada] .hoja-inferior-cabeza { border-block-end-color: var(--linea); }
.hoja-inferior-titulo {
  grid-row: 2;
  grid-column: 1;
  margin: 0;
  min-inline-size: 0;
  font-size: var(--t-titulo-3);
  line-height: 28px;
  font-weight: var(--p-normal);
  letter-spacing: -0.02em;
  text-wrap: balance;
  cursor: auto;
}
/* El título recibe el foco al abrir con el puntero (para el lector): no es un control */
.hoja-inferior-titulo:focus { outline: none; }
.hoja-inferior-cerrar { grid-row: 2; grid-column: 2; cursor: pointer; }

/* ---- Asa: el dibujo (32 × 4) en su zona (48 × 24), centrada en el borde ---- */
.hoja-inferior-asa {
  position: relative;
  grid-row: 1;
  grid-column: 1 / -1;
  justify-self: center;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  inline-size: var(--hi-asa-zona);
  block-size: var(--hi-asa-fila);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--linea-control);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui), background-color var(--m-t2) var(--m-ui);
}
.hoja-inferior-asa::before {
  content: "";
  display: block;
  inline-size: var(--hi-asa-ancho);
  block-size: var(--hi-asa-alto);
  border-radius: var(--r-s);
  background: currentColor;
}
/* Solo dibujo (todo cabe a media altura, o sin JS): no responde; se arrastra con la cabeza */
.hoja-inferior-asa[aria-hidden="true"] { cursor: inherit; }
@media (hover: hover) {
  .hoja-inferior-asa:not([aria-hidden="true"]):hover { color: var(--texto); }
}
.hoja-inferior-asa.es-encima { color: var(--texto); }
.hoja-inferior-asa:not([aria-hidden="true"]):active,
.hoja-inferior-asa.es-pulsado { color: var(--texto); background: var(--capa); }
.hoja-inferior-asa:focus { outline: none; }
.hoja-inferior-asa:focus-visible,
.hoja-inferior-asa.es-foco { outline: var(--trazo-foco) solid var(--foco); outline-offset: calc(-1 * var(--trazo-foco)); border-radius: var(--r); }

/* ---- Cuerpo: lo único que se desplaza ---- */
.hoja-inferior-cuerpo {
  min-block-size: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--e-8) var(--hi-lado) var(--e-24);
}

/* ---- Pie (opcional): las acciones, la principal delante ---- */
.hoja-inferior-pie {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e-12) var(--e-24);
  padding: var(--e-16) var(--hi-lado);
  border-block-start: var(--trazo) solid var(--linea);
}

/* ---- En un marco: la hoja no sale de él. El marco es su página (el ancho) y su caja
   (lo fijo y lo absoluto se quedan dentro); los altos, del marco ---- */
.hoja-inferior-marco {
  position: relative;
  container: pagina / inline-size;
  contain: layout paint;
  isolation: isolate;
  overflow: clip;
}
.hoja-inferior-marco > .hoja-inferior {
  --hi-medio: 50%;
  --hi-completo: 90%;
  position: absolute;
}
.hoja-inferior-velo {
  position: absolute;
  z-index: 44;
  inset: 0;
  background: var(--velo);
}
.hoja-inferior-velo[hidden] { display: none; }

/* ---- Desde 640: centrada como un diálogo o, con --lateral, a la derecha ---- */
@container pagina (min-width: 640px) {
  .hoja-inferior {
    --hi-forma: dialogo;
    --hi-fuera: 0 var(--e-4);
    --hi-fuera-opacidad: 0;
    --hi-entra: var(--m-t3);
    --hi-entra-curva: var(--m-ui);
    inset: 0;
    margin: auto;
    inline-size: min(var(--hi-ancho), 100% - 2 * var(--e-48));
    block-size: fit-content;
    padding: 0;
    border: var(--trazo) solid var(--linea-2);
    border-radius: var(--r);
  }
  .hoja-inferior,
  .hoja-inferior[data-alto] { max-block-size: min(var(--hi-completo), 100% - 2 * var(--e-48)); }
  .hoja-inferior--lateral {
    --hi-forma: lateral;
    --hi-fuera: 100% 0;
    --hi-fuera-opacidad: 1;
    --hi-entra: var(--m-t5);
    --hi-entra-curva: var(--m-traza);
    inset-block: 0;
    inset-inline: auto 0;
    margin: 0;
    inline-size: min(var(--hi-ancho), 100% - var(--e-48));
    block-size: auto;
    padding: env(safe-area-inset-top, 0px) env(safe-area-inset-right, 0px) env(safe-area-inset-bottom, 0px) 0;
    border: 0;
    border-inline-start: var(--trazo) solid var(--linea-2);
    border-radius: 0;
  }
  .hoja-inferior--lateral,
  .hoja-inferior--lateral[data-alto] { max-block-size: none; }
  .hoja-inferior--lateral:dir(rtl) { --hi-fuera: -100% 0; }
  .hoja-inferior-asa { visibility: hidden; }
  .hoja-inferior-cabeza { touch-action: auto; -webkit-user-select: auto; user-select: auto; cursor: auto; }
}

/* ---- Movimiento: solo con las llaves (html[data-mov] y sin «reducir movimiento») ---- */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .hoja-inferior[open] {
    transition:
      translate var(--hi-entra) var(--hi-entra-curva),
      opacity var(--hi-entra) var(--m-suave),
      max-block-size var(--m-t4) var(--m-ui);
  }
  html[data-mov] .hoja-inferior[open]::backdrop { transition: opacity var(--m-t5) var(--m-suave); }
  html[data-mov] .hoja-inferior-marco > .hoja-inferior-velo { transition: opacity var(--m-t5) var(--m-suave); }
  /* Entra desde fuera. El velo del marco, solo cuando se abre su hoja (un velo quieto, de
     la documentación, no se funde al cargar la página) */
  @starting-style {
    html[data-mov] .hoja-inferior[open] { translate: var(--hi-fuera); opacity: var(--hi-fuera-opacidad); }
    html[data-mov] .hoja-inferior[open]::backdrop { opacity: 0; }
    html[data-mov] .hoja-inferior-marco:has(> .hoja-inferior[open]) > .hoja-inferior-velo { opacity: 0; }
  }
  /* Sale: t3 con la curva de salida, desde donde esté (también desde el dedo) */
  html[data-mov] .hoja-inferior[open][data-paso="sale"] {
    translate: var(--hi-fuera);
    opacity: var(--hi-fuera-opacidad);
    transition: translate var(--m-t3) var(--m-va), opacity var(--m-t3) var(--m-va);
  }
  html[data-mov] .hoja-inferior[open][data-paso="sale"]::backdrop { opacity: 0; transition: opacity var(--m-t3) var(--m-va); }
  html[data-mov] .hoja-inferior-marco > .hoja-inferior-velo[data-paso="sale"] { opacity: 0; transition: opacity var(--m-t3) var(--m-va); }
  /* Soltada a medio camino: vuelve a su sitio */
  html[data-mov] .hoja-inferior[open][data-paso="vuelve"] { transition: translate var(--m-t3) var(--m-ui), max-block-size var(--m-t3) var(--m-ui); }
  /* Mientras la lleva el dedo, sin transición: la sigue */
  html[data-mov] .hoja-inferior[open][data-arrastre] { transition: none; }
}

/* ---- Con el dedo: la zona del asa crece a 44 hacia abajo (por arriba está el velo, que
   cierra), sin mover el dibujo ---- */
@media (any-pointer: coarse) {
  .hoja-inferior-asa::after {
    content: "";
    position: absolute;
    inset: 0 0 calc(var(--hi-asa-fila) - 44px);
  }
}

@media (forced-colors: active) {
  .hoja-inferior { border-color: CanvasText; }
  .hoja-inferior-asa { color: CanvasText; }
  .hoja-inferior[data-desplazada] .hoja-inferior-cabeza,
  .hoja-inferior-pie { border-color: CanvasText; }
  .hoja-inferior-velo { background: transparent; }
}

@media print {
  .hoja-inferior:is(dialog), .hoja-inferior-velo { display: none !important; }
}

/* ==== componentes/hojas-laterales.css ==== */
/* ==========================================================================
   Hoja lateral: trae al lado de la página lo que la completa sin salir de ella (el detalle
   de una factura, los filtros de una lista, un formulario corto). Entra por la derecha, a
   toda la altura. Un <dialog>: el navegador ya sabe abrirlo, cerrarlo y dejar inerte lo de
   detrás.
     <button type="button" commandfor="hoja-contacto" command="show-modal"
             aria-expanded="false">Editar los datos de contacto</button>
     <dialog class="hoja-lateral" id="hoja-contacto" aria-labelledby="hoja-contacto-titulo">
       <div class="hoja-lateral-cabecera">
         <div>
           <h2 class="hoja-lateral-titulo" id="hoja-contacto-titulo">Datos de contacto</h2>
           <p class="hoja-lateral-contexto">WSC · Worth Sapiens Consultants</p>
         </div>
         <button type="button" class="boton-icono boton-icono--contorno hoja-lateral-cerrar"
                 commandfor="hoja-contacto" command="close" aria-label="Cerrar">
           <svg class="i i-20" aria-hidden="true"><use href="#i-cerrar"/></svg></button>
       </div>
       <div class="hoja-lateral-cuerpo">
         <div class="hoja-lateral-grupo">…</div>
       </div>
       <div class="hoja-lateral-pie">
         <button type="submit" class="boton boton--primario">Guardar los cambios</button>
         <button type="button" class="boton boton--secundario" commandfor="hoja-contacto"
                 command="close">Cancelar</button>
       </div>
     </dialog>
   · Modal (la de siempre): encima de todo, en la capa superior, con el velo (--velo) en
     ::backdrop; la página de detrás queda inerte y quieta. En S (página de menos de 640), a
     pantalla completa.
   · .hoja-lateral--acoplada, dentro de un .hoja-lateral-marco (la página en
     .hoja-lateral-contenido y la hoja después): en L (página de 1024 o más, --hl-acopla: 1)
     abre su columna a la derecha, bajo la cabecera del producto (--hl-tope: 56 y su filete),
     y la página se estrecha a su lado, sin velo y a mano. Por debajo de L se abre modal.
   · [data-hoja-lateral-ambito]: la caja que hace de ventana (una pantalla de muestra, una
     región). La modal no sube a la capa superior: cubre esa caja con su velo
     (.hoja-lateral-velo, lo pone el JS) y deja inerte lo demás de ella.
   · Partes: cabecera (título, contexto opcional y cerrar), cuerpo (se desplaza entre la
     cabecera y el pie), grupos del cuerpo (.hoja-lateral-grupo, opcional) y pie (acciones,
     opcional).
   Medidas: 400 de ancho (--hl-ancho, siempre entre 360 y 480), a toda la altura y a sangre
   (sin radio: el radio es de lo que tiene sus cuatro lados a la vista). Fondo de la página y
   filete de 1 px en línea 2 a la izquierda; sin sombra: la separa el velo (modal) o el
   filete (acoplada). Cabecera de 16 · 24 con el título en título 3 (línea de 28, a 6 del
   borde para centrarse con cerrar) y cerrar de 40 × 40 (el icono de contorno en su medida S,
   44 con el dedo); cuerpo con 24 de margen, en menor (15/20); pie de 16 · 24 con los botones
   M a 12. La cabecera y el pie se separan del cuerpo con un filete de 1 en línea.
   Estado: el atributo open del diálogo (lo pone el navegador) y data-modo="modal" |
   "acoplada" (lo pone el JS al abrir). El movimiento es la receta del panel lateral de
   movimiento.css (.panel-lateral y .abierto, que pone y quita el JS): entra en t5 con la
   curva de trazos y sale en t3 con la de salida; el velo se funde a la vez y, en la
   acoplada, la columna se abre al mismo paso, así la hoja y la página se mueven juntas.
   Sin JS, el botón con commandfor la abre modal (en los navegadores que lo conocen) y ahí se
   queda, en su sitio y sin recorrido.
   ========================================================================== */
@property --hl-visible { syntax: "<length>"; inherits: true; initial-value: 0px; }

/* Los tokens van en el marco (los leen la hoja y la columna) o en la hoja suelta */
.hoja-lateral-marco,
.hoja-lateral:not(.hoja-lateral-marco > .hoja-lateral) {
  --hl-ancho: 400px;
  --hl-mide: clamp(360px, var(--hl-ancho), 480px);  /* el ancho, nunca fuera de 360–480 */
  --hl-lado: var(--e-24);                            /* margen de lado de la cabecera, el cuerpo y el pie */
  --hl-cerrar: 40px;                                 /* 44 con el dedo */
  /* La zona segura del teléfono: la hoja a pantalla completa no mete nada bajo la isla */
  --hl-arriba: env(safe-area-inset-top, 0px);
  --hl-abajo: env(safe-area-inset-bottom, 0px);
  --hl-derecha: env(safe-area-inset-right, 0px);
  /* Dónde empieza la acoplada: bajo la cabecera del producto (56 y su filete). Quien la usa
     lo cambia si su cabecera mide otra cosa */
  --hl-tope: calc(var(--hl-arriba) + 56px + var(--trazo));
}
/* En un ámbito (una pantalla de muestra) no hay isla ni muesca: la zona segura es de la ventana */
[data-hoja-lateral-ambito] :is(.hoja-lateral-marco, .hoja-lateral) { --hl-arriba: 0px; --hl-abajo: 0px; --hl-derecha: 0px; }
/* El ámbito es la caja de lo fijo de dentro: la hoja y el velo se quedan en él */
[data-hoja-lateral-ambito] { contain: layout paint; }

/* ---- La hoja ---- */
.hoja-lateral {
  display: none;
  box-sizing: border-box;
  grid-template-rows: auto minmax(0, 1fr) auto;
  inline-size: min(100%, var(--hl-mide));
  block-size: auto;                 /* entre sus dos bordes: a toda la altura */
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border: 0;
  border-inline-start: var(--trazo) solid var(--linea-2);
  border-radius: 0;
  background: var(--fondo);
  color: var(--texto);
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
}
/* Abierta (o quieta, en la documentación) */
.hoja-lateral:is([open], .es-quieta) { display: grid; }
.hoja-lateral[open] {
  position: fixed;
  z-index: 45;                      /* modal en un ámbito; en la capa superior no cuenta */
  inset-block: 0;
  inset-inline: auto 0;
  /* Siempre con traslación (0 en reposo): la hoja es el bloque de lo que lleva dentro y Safari
     lo pinta también cuando vive en un ámbito con contain (una pantalla de muestra) */
  translate: 0 0;
}
.hoja-lateral::backdrop { background: var(--velo); }

/* En S, la modal ocupa la pantalla: sin velo a la vista, sin filete */
@container pagina (max-width: 639px) {
  .hoja-lateral:is(:modal, [data-modo="modal"]) { inline-size: 100%; border-inline-start-width: 0; }
}

/* ---- Cabecera: el título (y su contexto) y cerrar, que se quedan arriba ---- */
.hoja-lateral-cabecera {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--e-16);
  padding-block: calc(var(--e-16) + var(--hl-arriba)) var(--e-16);
  padding-inline: var(--hl-lado) calc(var(--hl-lado) + var(--hl-derecha));
  border-block-end: var(--trazo) solid var(--linea);
}
.hoja-lateral-titulo {
  margin: 0;
  padding-block-start: 6px;         /* (40 − 28) / 2: la primera línea, al centro de cerrar */
  font-size: var(--t-titulo-3);
  line-height: 28px;
  letter-spacing: -0.02em;
  font-weight: var(--p-normal);
  text-wrap: balance;
  overflow-wrap: anywhere;
}
.hoja-lateral-contexto {
  margin: var(--e-4) 0 0;
  font-size: var(--t-nota);
  line-height: 16px;
  color: var(--texto-2);
}
/* Cerrar: el botón de icono de contorno de formas.css en su medida S (40, como el botón S) */
.hoja-lateral-cerrar {
  position: relative;
  inline-size: var(--hl-cerrar);
  block-size: var(--hl-cerrar);
}
/* El aspa de cerrar va en el color del texto también cuando la pieza no es un <button> (las
   muestras quietas): formas.css pinta en rojo de error el aspa que va suelta */
svg.i:has(> use[href="#i-cerrar"]):is(.hoja-lateral-cerrar > svg) { color: inherit; }
.hoja-lateral-cerrar.es-encima { border-color: var(--texto); }
.hoja-lateral-cerrar.es-pulsado { border-color: var(--texto); transform: translateY(1px); }

/* ---- Cuerpo: lo único que se desplaza ---- */
.hoja-lateral-cuerpo {
  min-block-size: 0;
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  padding-block: var(--e-24) var(--e-32);
  padding-inline: var(--hl-lado) calc(var(--hl-lado) + var(--hl-derecha));
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
}
/* Grupos del cuerpo (opcional): cada bloque con su rótulo, a 24 y tras un filete */
.hoja-lateral-grupo { display: grid; gap: var(--e-12); min-inline-size: 0; margin: 0; padding: 0; border: 0; }
.hoja-lateral-grupo + .hoja-lateral-grupo {
  margin-block-start: var(--e-24);
  padding-block-start: var(--e-24);
  border-block-start: var(--trazo) solid var(--linea);
}

/* ---- Pie (opcional): las acciones, la principal primero ---- */
.hoja-lateral-pie {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e-12);
  padding-block: var(--e-16) calc(var(--e-16) + var(--hl-abajo));
  padding-inline: var(--hl-lado) calc(var(--hl-lado) + var(--hl-derecha));
  border-block-start: var(--trazo) solid var(--linea);
}

/* ---- Modal en un ámbito: el velo, bajo la hoja (lo pone el JS justo antes de ella) ---- */
.hoja-lateral-velo { position: fixed; z-index: 44; inset: 0; background: var(--velo); }
.hoja-lateral-velo[hidden] { display: none; }

/* ---- Acoplada: el marco reparte página | columna; la hoja se fija en la columna ---- */
.hoja-lateral-marco {
  --hl-acopla: 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--hl-visible);
}
@container pagina (min-width: 1024px) {
  .hoja-lateral-marco { --hl-acopla: 1; }
}
.hoja-lateral-marco > .hoja-lateral-contenido { grid-column: 1; grid-row: 1; min-inline-size: 0; }
/* La columna mide lo que la hoja mientras está abierta: la página se estrecha a su lado */
.hoja-lateral-marco:has(> .hoja-lateral.abierto[data-modo="acoplada"]) { --hl-visible: var(--hl-mide); }
.hoja-lateral[data-modo="acoplada"] {
  z-index: 40;                      /* bajo la cabecera del producto (50) */
  inset-block-start: var(--hl-tope);
  inline-size: var(--hl-mide);
}

/* ---- Movimiento: la receta del panel lateral (movimiento.css, que se carga después), con sus
   llaves. Sin ellas, la hoja aparece y se va sin recorrido ---- */
.hoja-lateral.panel-lateral, .hoja-lateral.panel-lateral.abierto { transition: none; }
:dir(rtl) .hoja-lateral.panel-lateral:not(.abierto) { transform: translateX(-100%); }
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .hoja-lateral.panel-lateral { transition: transform var(--m-t3) var(--m-va); }
  html[data-mov] .hoja-lateral.panel-lateral.abierto { transition: transform var(--m-t5) var(--m-traza); }
  /* El velo se funde: sale con la hoja y entra con el fundido */
  html[data-mov] .hoja-lateral.panel-lateral::backdrop,
  html[data-mov] .hoja-lateral-velo { transition: opacity var(--m-t3) var(--m-va); }
  html[data-mov] .hoja-lateral.panel-lateral:not(.abierto)::backdrop,
  html[data-mov] .hoja-lateral-velo:not(:has(+ .hoja-lateral.abierto)) { opacity: 0; }
  html[data-mov] .hoja-lateral.panel-lateral.abierto::backdrop,
  html[data-mov] .hoja-lateral-velo:has(+ .hoja-lateral.abierto) { transition: opacity var(--m-t5) var(--m-suave); }
  /* La columna de la acoplada, al paso de la hoja */
  html[data-mov] .hoja-lateral-marco { transition: --hl-visible var(--m-t3) var(--m-va); }
  html[data-mov] .hoja-lateral-marco:has(> .hoja-lateral.abierto[data-modo="acoplada"]) { transition: --hl-visible var(--m-t5) var(--m-traza); }
}

/* Con la modal abierta sobre la ventana, la página de detrás no se desplaza */
html[data-hoja-lateral-modal] { overflow: hidden; scrollbar-gutter: stable; }

/* Con el dedo, cerrar responde en 44 sin crecer */
@media (any-pointer: coarse) {
  .hoja-lateral-cerrar::after { content: ""; position: absolute; inset: calc((var(--hl-cerrar) - 44px) / 2 - var(--trazo)); }
}

@media (prefers-reduced-motion: reduce) {
  .hoja-lateral, .hoja-lateral::backdrop, .hoja-lateral-velo, .hoja-lateral-marco { transition: none; }
}
@media (forced-colors: active) {
  .hoja-lateral { border-inline-start: var(--trazo) solid CanvasText; }
  .hoja-lateral:modal { border: var(--trazo) solid CanvasText; }
  .hoja-lateral-cabecera, .hoja-lateral-pie, .hoja-lateral-grupo + .hoja-lateral-grupo { border-color: CanvasText; }
}
@media print {
  .hoja-lateral, .hoja-lateral-velo { display: none !important; }
  .hoja-lateral-marco { display: block; }
}

/* ==== componentes/avisos-pagina.css ==== */
/* ==========================================================================
   Avisos en la página: lo que la persona tiene que saber y se queda en su sitio mientras
   sigue siendo verdad (falta algo para publicar, estás viendo un borrador, no se ha podido
   subir un fichero). No flota ni se va solo: eso es un aviso flotante.
     <div class="aviso-bloque" data-estado="aviso">
       <svg class="i i-16 aviso-bloque-icono" aria-hidden="true"><use href="#i-alerta"/></svg>
       <div class="aviso-bloque-cuerpo">
         <p class="aviso-bloque-titulo"><span class="solo-lector">Aviso: </span>Faltan los textos legales</p>
         <p class="aviso-bloque-texto">La web no se publica sin el aviso legal.</p>
         <p class="aviso-bloque-acciones"><a class="enlace-suelto" href="#legal">Añadir los textos</a></p>
       </div>
       <button type="button" class="aviso-bloque-cerrar" aria-label="Cerrar el aviso">…i-cerrar…</button>
     </div>
   · Cuatro estados, en data-estado: exito (i-check), aviso (i-alerta), error (i-alerta) e
     info (i-info; es el de partida, sin atributo). Cada uno toma sus cuatro roles de
     color.css: --{estado}-fondo, -borde, -texto e -icono. Nunca solo color: el icono, el
     título que dice qué pasa y, para el lector, la palabra del estado delante (solo-lector).
   · En línea (la de serie): dentro del contenido, junto a lo que afecta. Filete de 1 px en el
     borde de su estado, radio 4 y 16 de relleno.
   · .aviso-bloque--banda: lo que vale para toda la página, arriba y a todo el ancho, bajo la
     barra superior. Sin radio ni marco: solo el filete de abajo. El texto empieza en el
     margen de la retícula (--margen), como la página. En fila cuando cabe; si no, se apila.
   · .aviso-bloque--resumen: el resumen de errores de un formulario, arriba de él. Título con
     la cuenta («Revisa 2 campos») y .aviso-bloque-lista con un enlace a cada campo. Lleva
     tabindex="-1" y aria-labelledby a su título; role="alert" se lo pone el JS al enviar.
     Lo corregido (li[data-resuelto]) se tacha y pasa a texto 2, sin anunciarse.
   · .aviso-bloque-cerrar (opcional): solo en lo que no es crítico. Lo que sigue siendo
     verdad (un error por arreglar, un borrador) no se cierra: se va cuando deja de serlo.
   Medidas: icono de 16 (va con texto de 15) centrado en la primera línea de 20, a 12 del
   texto; título en 500 y texto en 400, 15/20, a 4; la acción a 12 del texto (4 + 8). El aspa
   en un cuadro de 32 que se come 6 arriba y abajo y 8 a la derecha: su dibujo queda a 16 del
   borde, como el icono, y la fila no crece. Encima, el cuadro enseña un filete de 1 px en el
   color del texto del estado (como el botón secundario); pulsado, además, baja 1 px. Con el
   dedo responde en 44 × 44 sin mover nada. Sin sombras; el texto nunca pasa de 32em.
   El color de enlace pasa a ser el del texto del estado (--enlace se redefine dentro): la
   acción y los enlaces del resumen no cambian de familia a mitad del aviso.
   ========================================================================== */
.aviso-bloque {
  --ab-fondo: var(--info-fondo);
  --ab-borde: var(--info-borde);
  --ab-texto: var(--info-texto);
  --ab-icono: var(--info-icono);
  --ab-relleno: var(--e-16);
  --ab-cerrar: 32px;
  --enlace: var(--ab-texto);
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  grid-auto-columns: auto;
  column-gap: var(--e-12);
  align-items: start;
  padding: var(--ab-relleno);
  border: var(--trazo) solid var(--ab-borde);
  border-radius: var(--r);
  background: var(--ab-fondo);
  color: var(--ab-texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  min-inline-size: 0;
}
.aviso-bloque[data-estado="exito"] { --ab-fondo: var(--exito-fondo); --ab-borde: var(--exito-borde); --ab-texto: var(--exito-texto); --ab-icono: var(--exito-icono); }
.aviso-bloque[data-estado="aviso"] { --ab-fondo: var(--aviso-fondo); --ab-borde: var(--aviso-borde); --ab-texto: var(--aviso-texto); --ab-icono: var(--aviso-icono); }
.aviso-bloque[data-estado="error"] { --ab-fondo: var(--error-fondo); --ab-borde: var(--error-borde); --ab-texto: var(--error-texto); --ab-icono: var(--error-icono); }

/* El icono de 16, centrado en la primera línea de 20 */
.aviso-bloque-icono { grid-column: 1; grid-row: 1; margin-block-start: 2px; color: var(--ab-icono); }

.aviso-bloque-cuerpo { grid-column: 2; grid-row: 1; display: grid; gap: var(--e-4); min-inline-size: 0; }
.aviso-bloque-cuerpo > * { margin: 0; max-inline-size: var(--medida); }
/* El título puede ser un titular (h2, h3) donde el aviso abre algo: se queda a su tamaño */
.aviso-bloque-titulo { font-size: inherit; line-height: inherit; letter-spacing: inherit; font-weight: var(--p-medio); text-wrap: pretty; }
.aviso-bloque-texto { font-weight: var(--p-normal); text-wrap: pretty; }
.aviso-bloque-acciones { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--e-8) var(--e-24); margin-block-start: var(--e-8); }

/* Los enlaces del texto y del resumen, en el color del estado; el subrayado de puntos del
   sistema, en su color */
.aviso-bloque :where(a:not([class])) { color: inherit; text-decoration-color: color-mix(in oklab, currentColor 60%, transparent); }

/* ---- Resumen de errores: la lista de enlaces, a 12 del título y a 8 entre sí ---- */
.aviso-bloque-lista { display: grid; gap: var(--e-8); margin: var(--e-8) 0 0; padding: 0; list-style: none; }
.aviso-bloque-lista li[data-resuelto] a { color: var(--texto-2); text-decoration-line: line-through; text-decoration-style: solid; text-decoration-color: currentColor; }

/* ---- Cerrar: el aspa en un cuadro de 32, sin marco en reposo ---- */
.aviso-bloque-cerrar {
  position: relative;
  grid-column: 3;
  grid-row: 1;
  display: inline-grid;
  place-items: center;
  inline-size: var(--ab-cerrar);
  block-size: var(--ab-cerrar);
  margin-block: calc((20px - var(--ab-cerrar)) / 2);
  margin-inline: 0 calc(8px - var(--ab-cerrar) / 2);
  padding: 0;
  border: var(--trazo) solid transparent;
  border-radius: var(--r);
  background: none;
  color: var(--ab-texto);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: border-color var(--m-t2) var(--m-ui), transform var(--m-t1) var(--m-ui);
}
.aviso-bloque-cerrar .i { color: inherit; }
/* Encima (con puntero) y pulsado. .es-encima y .es-pulsado enseñan el estado quieto */
@media (hover: hover) {
  .aviso-bloque-cerrar:hover { border-color: var(--ab-texto); }
}
.aviso-bloque-cerrar.es-encima { border-color: var(--ab-texto); }
.aviso-bloque-cerrar:active,
.aviso-bloque-cerrar.es-pulsado { border-color: var(--ab-texto); transform: translateY(1px); }

/* ---- Banda: arriba de la página, a todo el ancho ---- */
.aviso-bloque--banda {
  --ab-relleno: var(--e-12);
  border-width: 0 0 var(--trazo);
  border-radius: 0;
  /* El texto, en el margen de la retícula (y nunca bajo la muesca del iPhone apaisado) */
  padding-inline: max(var(--margen, var(--e-24)), env(safe-area-inset-left, 0px)) max(var(--margen, var(--e-24)), env(safe-area-inset-right, 0px));
}
/* Título, texto y acción en una fila cuando caben; si no, uno bajo otro */
.aviso-bloque--banda .aviso-bloque-cuerpo { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--e-4) var(--e-12); }
.aviso-bloque--banda .aviso-bloque-acciones { margin: 0; }

/* Con el dedo, todo lo que se pulsa responde en 44 de alto sin mover su dibujo: el aspa en
   44 × 44 (32 + 6 por lado); la acción, 12 arriba y abajo de su línea de 20 (el enlace suelto
   del sistema cuenta con una de 24). En el resumen, cada enlace ocupa su fila con 12 arriba y
   abajo y la lista se abre a 24: las zonas se tocan y no se pisan (20 + 24 = 44) */
@media (any-pointer: coarse) {
  /* el ::after se mide desde dentro del borde: se descuenta su trazo (si no, 42) */
  .aviso-bloque-cerrar::after { content: ""; position: absolute; inset: calc((var(--ab-cerrar) - 44px) / 2 - var(--trazo)); }
  .aviso-bloque-acciones .enlace-suelto::after { inset-block: -12px; }
  .aviso-bloque-lista { gap: var(--e-24); }
  .aviso-bloque-lista a { display: block; padding-block: var(--e-12); margin-block: calc(-1 * var(--e-12)); }
}

/* Movimiento: el fundido de entrada y salida lo hace el JS (240 ms), solo con html[data-mov]
   y sin «reducir movimiento». Aquí, solo la respuesta del aspa */
@media (prefers-reduced-motion: reduce) {
  .aviso-bloque-cerrar { transition: none; }
  .aviso-bloque-cerrar:active, .aviso-bloque-cerrar.es-pulsado { transform: none; }
}
@media (forced-colors: active) {
  .aviso-bloque { border-color: CanvasText; }
  .aviso-bloque-icono { color: CanvasText; }
  .aviso-bloque-cerrar { color: ButtonText; }
  .aviso-bloque-cerrar:hover, .aviso-bloque-cerrar.es-encima { border-color: Highlight; }
  .aviso-bloque-lista li[data-resuelto] a { color: GrayText; }
}
@media print {
  .aviso-bloque-cerrar { display: none; }
}

/* ==== componentes/avisos.css ==== */
/* ==========================================================================
   Aviso flotante: dice, abajo y durante unos segundos, lo que acaba de pasar.
   Lo pone WSC.avisar (js/componentes/avisos.js): uno a la vez, en cola. Sin JS no hay
   aviso y nada queda a medias: lo hecho está hecho y se ve en su sitio.
     WSC.avisar('Enlace copiado');
     WSC.avisar({ texto: 'Proyecto archivado', accion: { texto: 'Deshacer', fn: fn } });
   El HTML que pone (una caja por sitio, que se queda y se vuelve a usar):
     <div class="aviso-flotante" data-estado="dentro">
       <p class="aviso-flotante-texto" role="status">Proyecto archivado</p>
       <div class="aviso-flotante-botones">
         <button type="button" class="enlace-suelto aviso-flotante-accion">Deshacer</button>
         <button type="button" class="aviso-flotante-cerrar" aria-label="Cerrar el aviso">
           <svg class="i i-16" aria-hidden="true"><use href="#i-cerrar"/></svg></button>
       </div>
     </div>
   · Solo texto: 4 s, sin cerrar. Con acción: 10 s, con la acción y cerrar. duracion lo cambia
     entre 4 y 10 s; cerrar sale siempre que dura más de 4.
   · data-estado: «dentro» (a la vista), «sale» (se va) y «fuera» (vacía y recortada, como
     .solo-lector: la región viva sigue en el árbol del lector y lo que se escribe en ella
     se anuncia). Sin data-estado, la caja se ve quieta: así la enseña la documentación.
   Dibujo: el del aviso del cuentagotas. En negativo (el color del texto de fondo y el del
   fondo en la letra), radio 4 y sin borde ni sombra: el negativo lo separa de la página.
   48 de alto con una línea: un marco de 4 y, dentro, botones de 40 (el alto del botón S).
   El texto, en --t-menor (15 / 20), a 16 del borde (4 + 12) y con 10 arriba y abajo, que
   centran la línea de 20 en los 40 del botón. La acción y cerrar van juntos
   (.aviso-flotante-botones): si no caben en la línea del texto, bajan a otra y la ocupan
   entera, la acción al principio, con la letra en la vertical del texto (4 + 12), y cerrar
   al final. Sin acción, cerrar no baja: el texto se parte a su lado.
   La acción es un enlace suelto en el color de enlace del otro tema (--accion-texto: menta
   sobre Tinta en claro; verde sobre neutro-50 en oscuro), en 500, con 12 a los lados.
   Cerrar, 40 × 40 con el aspa de 16. Las placas de dentro van con radio 0 (4 − 4) y el anillo
   de foco, por dentro de cada botón, en el mismo color que la acción.
   Dónde sale: abajo, a 16 del borde (más la zona segura), en la línea del margen del
   contenido: salta lo que ocupa el lado (--af-lado: el menú fijo de la herramienta; un
   carril, en un producto) y, pasado el contenedor (1600), sigue a la retícula centrada. En M
   y L, tan ancho como su texto, que no pasa de 32em de línea. En S (@container pagina), a todo
   el ancho menos los márgenes; con barra de navegación en el mismo sitio (hermana anterior),
   16 por encima de ella. Cuelga del contenido de la página (.lienzo, el contenedor «pagina»),
   así toma su tramo y su margen; con un diálogo modal abierto, de él.
   Capa: z-index 42, sobre la barra de navegación (40) y bajo los velos y paneles (44–45) y la
   cabecera (50).
   ========================================================================== */
.aviso-flotante {
  --af-boton: 40px;                          /* acción y cerrar: el alto del botón S */
  --af-marco: var(--e-4);                    /* del borde a los botones: 4 + 40 + 4 = 48 */
  --af-linea-texto: 20px;
  --af-fondo: var(--texto);                  /* negativo */
  --af-color: var(--fondo);
  --af-accion: var(--accion-texto);          /* el color de enlace del otro tema */
  --af-foco: var(--accion-texto);
  --af-placa: color-mix(in oklab, var(--af-color) 16%, var(--af-fondo));
  --af-placa-pulsado: color-mix(in oklab, var(--af-color) 24%, var(--af-fondo));
  --af-lado: var(--menu-visible, 0px);       /* lo que ocupa el lado de la ventana */
  --af-sobre: env(safe-area-inset-bottom, 0px); /* lo que tapa el borde de abajo */
  --af-inicio: max(var(--margen), env(safe-area-inset-left, 0px), (100% - var(--af-lado) - var(--contenedor)) / 2 + var(--margen));
  position: fixed;
  z-index: 42;
  inset-block-end: calc(var(--af-sobre) + var(--e-16));
  inset-inline-start: calc(var(--af-lado) + var(--af-inicio));
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  box-sizing: border-box;
  inline-size: fit-content;
  max-inline-size: calc(100% - var(--af-lado) - 2 * var(--af-inicio));
  min-block-size: calc(var(--af-boton) + 2 * var(--af-marco));
  margin: 0;
  padding: var(--af-marco);
  border-radius: var(--r);
  background: var(--af-fondo);
  color: var(--af-color);
  font-size: var(--t-menor);
  line-height: var(--af-linea-texto);
  letter-spacing: -0.01em;
  -webkit-tap-highlight-color: transparent;
}
/* Una pantalla propia (la sala de Presentar, las pantallas de la documentación) no tiene menú
   al lado: el aviso se coloca en su caja */
.pagina-propia > .aviso-flotante { --af-lado: 0px; }

/* El texto se queda con el aire de su línea (1000 contra 1): los botones, solos en la suya,
   la ocupan entera */
.aviso-flotante-texto {
  flex: 1000 1 auto;
  min-inline-size: 0;
  max-inline-size: 32em;
  margin: 0;
  padding: calc((var(--af-boton) - var(--af-linea-texto)) / 2) var(--e-12);
  overflow-wrap: break-word;
}

.aviso-flotante-botones { display: flex; flex: 1 0 auto; align-items: flex-start; }
.aviso-flotante [hidden] { display: none; }
/* Sin acción, cerrar se queda en la línea del texto */
.aviso-flotante:not(:has(.aviso-flotante-accion:not([hidden]))) { flex-wrap: nowrap; }

/* La acción: el enlace suelto, en el color de enlace del otro tema y en 500 */
.aviso-flotante-accion.enlace-suelto {
  position: relative;
  flex: none;
  align-items: center;
  block-size: var(--af-boton);
  padding: 0 var(--e-12);
  border-radius: 0;
  color: var(--af-accion);
  font-weight: var(--p-medio);
  white-space: nowrap;
  -webkit-tap-highlight-color: transparent;
}
.aviso-flotante-accion.es-encima,
.aviso-flotante-accion.es-pulsado { text-decoration-style: solid; text-decoration-color: currentColor; }

/* Cerrar: el aspa de 16 en 40 × 40; encima, una placa del color de la letra al 16 % */
.aviso-flotante-cerrar {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  inline-size: var(--af-boton);
  block-size: var(--af-boton);
  margin: 0;
  margin-inline-start: auto; /* al final, también cuando los botones bajan */
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--af-color);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .aviso-flotante-cerrar:hover { background: var(--af-placa); }
}
.aviso-flotante-cerrar.es-encima { background: var(--af-placa); }
.aviso-flotante-cerrar:active,
.aviso-flotante-cerrar.es-pulsado { background: var(--af-placa-pulsado); }

/* Foco: el anillo común, por dentro de cada botón y en el color de la acción (sobre el
   negativo, el del tema no se vería) */
.aviso-flotante :is(.aviso-flotante-accion, .aviso-flotante-cerrar):focus-visible,
.aviso-flotante :is(.aviso-flotante-accion, .aviso-flotante-cerrar).es-foco {
  outline: var(--trazo-foco) solid var(--af-foco);
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: 0;
}

/* Con el dedo, la acción y cerrar responden en 44 (miden 40) sin mover nada: la zona crece 2
   por cada lado, dentro del marco de 4 */
@media (any-pointer: coarse) {
  .aviso-flotante-accion::after, .aviso-flotante-cerrar::after { content: ""; position: absolute; inset: -2px 0; }
  .aviso-flotante-cerrar::after { inset-inline: -2px; }
}

/* Fuera: vacía, recortada y sin responder, como .solo-lector; la región viva sigue ahí */
.aviso-flotante[data-estado="fuera"] { clip-path: inset(50%); pointer-events: none; }

/* S: a todo el ancho menos los márgenes y, con la barra de navegación, por encima de ella
   (su alto de 64, su filete y la zona segura) */
@container pagina (max-width: 639px) {
  .aviso-flotante {
    inset-inline-end: max(var(--margen), env(safe-area-inset-right, 0px));
    inline-size: auto;
    max-inline-size: none;
  }
  .barra-navegacion:not([hidden]) ~ .aviso-flotante { --af-sobre: calc(64px + var(--trazo) + env(safe-area-inset-bottom, 0px)); }
}

/* Receta «contextual»: entra subiendo 4 px en t3 con la curva de respuesta y se va igual,
   con la de salida. Con «reducir movimiento», aparece y se va sin recorrido */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .aviso-flotante[data-estado="dentro"] { animation: af-entra var(--m-t3) var(--m-ui); }
  html[data-mov] .aviso-flotante[data-estado="sale"] { animation: af-sale var(--m-t3) var(--m-va) forwards; }
}
@keyframes af-entra { from { opacity: 0; translate: 0 var(--e-4); } }
@keyframes af-sale { to { opacity: 0; translate: 0 var(--e-4); } }
@media (prefers-reduced-motion: reduce) {
  .aviso-flotante-cerrar, .aviso-flotante-accion { transition: none; }
}

@media (forced-colors: active) {
  .aviso-flotante { outline: var(--trazo) solid CanvasText; }
  .aviso-flotante-accion.enlace-suelto { color: LinkText; }
  .aviso-flotante-cerrar { color: ButtonText; }
  .aviso-flotante :is(.aviso-flotante-accion, .aviso-flotante-cerrar):focus-visible { outline-color: Highlight; }
}
@media print { .aviso-flotante { display: none; } }

/* ==== componentes/globos.css ==== */
/* ==========================================================================
   Globo de ayuda: una etiqueta breve que aparece junto a un control. Sobre todo, el
   nombre de un botón de solo icono al pasar el puntero o al llegar con el teclado.
   Va en la capa superior del navegador (popover="manual"): no mueve nada ni lo corta
   un contenedor. js/componentes/globos.js lo enseña, lo coloca y lo quita.
   · Nombre (lo normal): el control lleva su nombre en aria-label y data-globo. El JS
     crea el globo detrás del control con ese mismo texto y aria-hidden: se ve lo que
     el lector ya dice, sin leerlo dos veces. Si el aria-label cambia, el globo también.
       <button class="boton-icono boton-icono--contorno" type="button" aria-label="Descargar"
               data-globo><svg class="i i-20" aria-hidden="true"><use href="#i-descargar"/></svg></button>
   · Descripción: un detalle que sobra (el atajo de teclado). Se escribe en el HTML, detrás
     del control, con role="tooltip"; el control lo enlaza con aria-describedby y el lector
     lo lee tras el nombre, aunque no se vea.
       <button class="boton boton--secundario boton--s" type="button"
               aria-describedby="g-buscar">Buscar</button>
       <span class="globo" id="g-buscar" role="tooltip" popover="manual">Pulsa / para buscar</span>
   · Informativo (.globo--info): un conmutador con el icono info (.globo-conmutador) lo
     abre y lo cierra al pulsar; lleva título, texto y, si hace falta, un enlace. Va dentro
     de una región role="status" que existe siempre (.globo-voz): al abrirse, lo que
     aparece en ella lo lee el lector. popover (auto): sin JS ya se abre y se cierra (en el
     centro de la ventana); un clic fuera o Esc lo cierran.
       <button type="button" class="globo-conmutador" aria-label="Por qué te preguntamos
               el presupuesto" aria-controls="g-presupuesto" popovertarget="g-presupuesto">
         <svg class="i i-16" aria-hidden="true"><use href="#i-info"/></svg></button>
       <span class="globo-voz" role="status">
         <span class="globo globo--info" id="g-presupuesto" popover>
           <span class="globo-titulo">…</span><span class="globo-texto">…</span>
           <a class="enlace-suelto" href="…">…</a></span></span>
   Superficie en negativo, como el aviso: el color del texto de fondo y el del fondo de
   letra (17,40:1 en claro, 16,15:1 en oscuro; sobre la banda verde, blanca con la letra
   verde, 9,43:1). Nota de 13/16, relleno de 4 × 8 (24 de alto en una línea), radio 4,
   hasta 240 de ancho. Sin flecha ni sombra: el negativo ya lo separa de la página.
   El informativo: 13/20, relleno de 12 × 16, hasta 320; título en 500, a 4 del texto;
   el enlace, a 8. Dentro, el enlace y el anillo del foco van en el color de enlace del
   otro tema, como en el aviso (--accion-texto: menta sobre Tinta en claro, 13,17:1; verde
   sobre blanco en oscuro y en la banda verde, 8,75 y 9,43:1): el verde y la menta del foco
   de la página no se ven sobre el negativo (1,85:1 y 1,23:1).
   Colocación: encima del control, centrado en él y a 8 (el anillo del foco, 3 + 2, cabe
   entre los dos con 3 de aire); si arriba no cabe, debajo; nunca a menos de 8 del borde
   de la ventana. Con el puntero sale a los 500 ms y se queda mientras está encima del
   control o del globo; con el teclado, al momento; se va al salir, al perder el foco,
   al pulsar y con Esc (WCAG 1.4.13). Con el dedo no sale: el nombre ya está en aria-label.
   Sin popover el globo quieto (la documentación) se dibuja en su sitio.
   ========================================================================== */
.globo {
  --gl-sep: var(--e-8);        /* del control al globo */
  --gl-margen: var(--e-8);     /* del globo al borde de la ventana */
  --gl-ancho: 240px;
  /* La espera con el puntero no es un recorrido: medio segundo, lo que tarda en notarse
     que el puntero se ha parado. La lee el JS */
  --gl-espera: 500ms;
  /* En negativo, el enlace y el foco, en el color de enlace del otro tema */
  --enlace: var(--accion-texto);
  --foco: var(--accion-texto);
  box-sizing: border-box;
  max-inline-size: var(--gl-ancho);
  margin: 0;
  padding: var(--e-4) var(--e-8);
  border: 0;
  border-radius: var(--r);
  background: var(--texto);
  color: var(--fondo);
  font-family: var(--f-haas);
  font-size: var(--t-nota);
  font-style: normal;
  font-weight: var(--p-normal);
  line-height: 16px;
  letter-spacing: 0;
  text-align: start;
  text-transform: none;
  white-space: normal;
  overflow-wrap: break-word;
}
/* Quieto, en su sitio (la documentación): a la medida de su texto y, si no cabe, a la de
   su sitio. Con popover, el display es del navegador: cerrado, lo quita */
.globo:not([popover]) { display: inline-block; vertical-align: top; }

/* En la capa superior, a la medida de su texto (el navegador lo ajusta), nunca más ancho
   que la ventana (el 100 % es la ventana) */
.globo[popover] {
  max-inline-size: min(var(--gl-ancho), 100% - 2 * var(--gl-margen));
  overflow: visible;
}
/* Colocado por el JS, en coordenadas de la ventana. Sin JS, el navegador lo centra */
.globo[popover][data-colocado] {
  inset: auto;
  top: var(--gl-y, 0px);
  left: var(--gl-x, 0px);
}
/* El puente: cubre los 8 que lo separan del control, para pasar del uno al otro sin
   que se vaya */
.globo[popover]::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  block-size: var(--gl-sep);
}
.globo[popover][data-lado="abajo"]::before { inset-block: auto 100%; }

/* ---- Informativo: título, texto y enlace ---- */
.globo--info {
  --gl-ancho: 320px;
  padding: var(--e-12) var(--e-16);
  line-height: 20px;
}
.globo-titulo { display: block; font-weight: var(--p-medio); }
.globo-texto { display: block; }
.globo-titulo + .globo-texto { margin-block-start: var(--e-4); }
.globo--info .enlace-suelto { display: flex; inline-size: fit-content; margin-block-start: var(--e-8); }

/* ---- El conmutador del informativo: el icono info de 16 en una caja de 24 ----
   Texto 2 en reposo (5,62:1); encima, pulsado y abierto, el color del texto sobre capa,
   como el botón que abre un menú. La zona crece a 44 con el dedo sin mover el dibujo.
   .es-encima y .es-pulsado enseñan el estado quieto en la documentación */
.globo-conmutador {
  --gl-caja: 24px;
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--gl-caja);
  block-size: var(--gl-caja);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--texto-2);
  font: inherit;
  vertical-align: middle;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.globo-conmutador .i { inline-size: 16px; block-size: 16px; transition: translate var(--m-t1) var(--m-ui); }
@media (hover: hover) {
  .globo-conmutador:hover { background: var(--capa); color: var(--texto); }
}
.globo-conmutador.es-encima,
.globo-conmutador[aria-expanded="true"] { background: var(--capa); color: var(--texto); }
.globo-conmutador:active,
.globo-conmutador.es-pulsado { background: var(--capa); color: var(--texto); }
.globo-conmutador:active .i,
.globo-conmutador.es-pulsado .i { translate: 0 1px; }
@media (any-pointer: coarse) {
  .globo-conmutador::before { content: ""; position: absolute; inset: -10px; }
}

/* ---- Movimiento: la receta contextual. Llega en 240 ms desde 4 px, del lado del
   control, con la curva de respuesta; se va al momento. Sin escala ---- */
@supports selector(:popover-open) {
  @media (prefers-reduced-motion: no-preference) {
    html[data-mov] .globo[popover]:popover-open {
      transition: opacity var(--m-t3) var(--m-ui), translate var(--m-t3) var(--m-ui);
    }
    @starting-style {
      html[data-mov] .globo[popover]:popover-open { opacity: 0; translate: 0 var(--e-4); }
      html[data-mov] .globo[popover][data-lado="abajo"]:popover-open { translate: 0 calc(-1 * var(--e-4)); }
    }
  }
}
/* Un navegador sin capa superior no sabe quitar el popover: el globo que describe no se
   ve (el lector lo sigue leyendo) y el informativo queda a la vista, en su sitio */
@supports not selector(:popover-open) {
  .globo[popover]:not(.globo--info) { display: none; }
  .globo--info[popover] { display: block; margin-block-start: var(--e-8); }
}

/* Movimiento reducido: aparece sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .globo-conmutador, .globo-conmutador .i { transition: none; }
}
@media (forced-colors: active) {
  .globo { outline: var(--trazo) solid CanvasText; }
  .globo-conmutador[aria-expanded="true"] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
}
@media print {
  .globo[popover] { display: none; }
}

/* ==== componentes/distintivos.css ==== */
/* ==========================================================================
   Distintivo: una cifra pegada a otra pieza. Dice que en un destino hay algo que espera
   (mensajes sin leer, una factura vencida, un artículo nuevo) o, en su variante de recuento,
   cuántos hay en una vista. Una cifra o, si basta saber que hay algo, un cuadro sin cifra.
   No se pulsa ni recibe el foco: responde el destino que lo lleva. Es el único distintivo del
   sistema: lo usan la barra de navegación y el carril (sobre el icono), las pestañas (el
   recuento, detrás del texto) y el botón «Filtrar» de los listados de la web (detrás del texto).
     Sobre el icono (un destino de la barra de navegación o del carril, un botón de icono):
       <span class="distintivo-ancla">
         <svg class="i i-24" aria-hidden="true"><use href="#i-chat"/></svg>
         <span class="distintivo" aria-hidden="true">3</span>
       </span>
     Detrás del texto, en una fila de menú (flex y centrada, como la del menú lateral):
       <a class="menu-lateral-fila" href="/mensajes">Mensajes
         <span class="distintivo" aria-hidden="true">3</span>
         <span class="solo-lector distintivo-lector" data-uno=", {n} sin leer"
           data-varios=", {n} sin leer">, 3 sin leer</span></a>
     Recuento, detrás del texto de una pestaña (se lee: sin aria-hidden):
       <a class="fila-pestanas-pestana" href="/proyectos"><span class="fila-pestanas-texto">Proyectos</span>
         <span class="distintivo distintivo--recuento">14</span></a>
   · Cifra (.distintivo): 16 de alto y desde 16 de ancho, relleno 0 4 y radio 4. La cifra, en
     --t-rotulo (12) sin mayúsculas ni interletraje (el +0,08 em del rótulo la descentraría),
     en Haas 500 (a 12 sobre color, el 400 se queda fino) y con cifras tabulares: es una
     cantidad, y la mono solo numera. De 1 a 99; desde 100, «99+»: una tercera cifra tapa más
     icono y no ayuda a decidir. A 0 no se enseña (hidden): nunca un «0».
   · Punto (.distintivo--punto): sin cifra, cuando basta saber que hay algo nuevo. Un cuadro de
     8 × 8 con radio 2 (--r-s, pieza de menos de 12): nada de puntos redondos.
   · Urgente (.distintivo--urgente): lo que vence o ha fallado y pide respuesta. El rojo de los
     iconos de error (--error-icono) con la cifra en el color del fondo. El rojo refuerza lo que
     dice la frase para el lector («1 vencida»); no va solo.
   · Recuento (.distintivo--recuento): cuántos hay en una vista (14 proyectos), no lo que espera.
     El cuadro, en capa, con la cifra en el color del texto que acompaña (el de la pestaña:
     texto 2 en reposo, texto en la activa y encima; 5,04:1 o más). Se lee con ese texto, así que
     toma su escala: 20 de alto y desde 20 de ancho, cifra de 13 (--t-nota) en 400, la línea de un
     texto de 15; sobre capa el 400 no se queda fino. Se queda: el 0 se escribe, no hay tope y
     lleva punto de millar («1.250»). Se oye con el nombre («Proyectos 14»): sin aria-hidden. Va
     detrás del texto, nunca sobre un icono.
   · Color: el cuadro en el de acción y la cifra en su texto (menta sobre verde en claro; verde
     sobre menta en oscuro y en la banda verde), 7,13:1. Urgente: 7,13 en claro, 9,01 en oscuro
     y 4,88 en la banda verde.
   Colocación (la da el sitio, no una clase del distintivo)
   · Sobre el icono (dentro de .distintivo-ancla, que envuelve el icono o lo que lleve la señal):
     centrado en la esquina de arriba al final del icono. Sale la mitad de su alto por arriba y
     por el lado (8 la cifra, 4 el punto): el icono se sigue leyendo entero y, con más cifras,
     crece hacia fuera sin tapar más icono. Alrededor, 2 px de aire en el color del fondo
     (outline, --di-aire; si no, --fondo): separa el cuadro del trazo del icono donde lo pisa.
     No es un borde: sobre el fondo no se ve. Si el fondo bajo el icono cambia (el destino encima
     o pulsado), quien lo coloca da ese fondo a --di-aire en el destino y el aire lo sigue en
     160 ms (el carril: capa encima y al pulsar; la barra de navegación: la capa de su placa).
     Lo que sale por arriba, 8 y el aire, mide 10: si el icono está a 10 o menos de un borde de
     arriba, --di-baja lo baja en el destino y el aire no toca ese borde. La barra de navegación,
     con el icono a 10 de su filete, lo baja 2. Por qué así: centrado en la esquina, el cuadro
     tapa 8 × 8 del icono y el icono se sigue leyendo (metido 12, tapa un cuarto y el chat o la
     factura dejan de reconocerse); y 2 de aire lo despegan del trazo, que un filete de 1 casi toca.
   · Detrás del texto (en el flujo, fuera del ancla): a 8 del nombre, el hueco de la fila o del
     botón, y centrado con la mayúscula: en la Haas la mayúscula cae 1 px por debajo del centro de
     su línea, y el distintivo baja con ella. Sin aire: no hay trazo que cortar.
   La cifra se centra por su tinta, como la del sello de IA: la línea, recortada a la altura de
   las cifras y a la base, y centrada; el relleno de abajo (0,712 em − 8 px) deja la base a 12
   px exactos del borde de arriba (8,54 de cifra, 3,46 de aire arriba y 4 abajo), a 1x y a 2x.
   El mismo cálculo deja la del recuento a 14 de los 20: siempre (alto + 8) / 2, un píxel entero.
   Lo que dice, en palabras: el distintivo lleva aria-hidden y la frase va en el nombre del
   destino, en un .solo-lector (.distintivo-lector): «Mensajes, 3 sin leer». La cifra sola no
   dice de qué es. js/componentes/distintivos.js (WSC.distintivo) cambia a la vez la cifra y la
   frase. Sin movimiento propio (aparece, cambia y se va al instante) ni estados de puntero:
   los del destino. Ningún distintivo se deshabilita ni se atenúa.
   ========================================================================== */
.distintivo {
  --di-alto: 16px;
  --di-fondo: var(--accion);
  --di-texto: var(--accion-texto);
  display: inline-block;
  flex: none;
  align-self: center;
  box-sizing: border-box;
  min-inline-size: var(--di-alto);
  block-size: var(--di-alto);
  padding: 0 var(--e-4) calc(.712em - 8px);
  border-radius: var(--r);
  background: var(--di-fondo);
  color: var(--di-texto);
  font-size: var(--t-rotulo);
  line-height: 1;
  font-weight: var(--p-medio);
  letter-spacing: 0;
  text-transform: none;
  font-variant-numeric: tabular-nums;
  text-align: center;
  text-indent: 0;
  white-space: nowrap;
  vertical-align: middle;
  align-content: center;
  text-box: trim-both cap alphabetic;
  /* Detrás del texto: centrado con la mayúscula, 1 px por debajo del centro de la línea */
  translate: 0 1px;
}
/* Sin text-box, la línea mide 1 em y su base cae a 0,913 em de arriba */
@supports not (text-box: trim-both cap alphabetic) {
  .distintivo { padding-block-end: calc(.826em - 8px); }
}
/* A 0 no se enseña: ni un «0» ni el hueco */
.distintivo[hidden] { display: none; }

/* Punto: el cuadro de 8, sin cifra */
.distintivo--punto {
  --di-alto: 8px;
  padding: 0;
  border-radius: var(--r-s);
  overflow: hidden;
}

/* Urgente: el rojo de error, la cifra en el color del fondo */
.distintivo--urgente {
  --di-fondo: var(--error-icono);
  --di-texto: var(--fondo);
}

/* Recuento: cuántos hay, y se lee con el texto al que acompaña. En capa, con la cifra en el color
   de ese texto (sigue a la pestaña en reposo, encima y activa, con su transición). Cuadro de 20 y
   cifra de 13 en 400: la línea y el tamaño de nota junto a un texto de 15; sobre capa, el 400 no
   se queda fino. El relleno de abajo (el mismo cálculo) deja la base a 14 de arriba */
.distintivo--recuento {
  --di-alto: 20px;
  --di-fondo: var(--capa);
  --di-texto: currentColor;
  font-size: var(--t-nota);
  font-weight: var(--p-normal);
}

/* ---- Sobre el icono: el ancla envuelve el icono; el distintivo, centrado en su esquina ---- */
.distintivo-ancla {
  position: relative;
  display: inline-grid;
  flex: none;
  vertical-align: middle;
}
.distintivo-ancla > .distintivo {
  position: absolute;
  /* El centro, en la esquina de arriba al final del icono (--di-baja lo baja si el icono está
     cerca de un borde). El principio queda fijo: si crece, crece hacia fuera */
  inset-block-start: calc(var(--di-baja, 0px) - var(--di-alto) / 2);
  inset-inline-start: calc(100% - var(--di-alto) / 2);
  translate: none;
  /* 2 de aire en el color de lo que hay debajo: separa el cuadro del trazo del icono */
  outline: 2px solid var(--di-aire, var(--fondo));
  outline-offset: 0;
  transition: outline-color var(--m-t2) var(--m-ui);
}

/* No se pulsa: sin zona táctil propia (la del destino ya mide 44 o más). Movimiento reducido:
   el aire cambia al instante */
@media (prefers-reduced-motion: reduce) {
  .distintivo-ancla > .distintivo { transition: none; }
}
/* Colores forzados: el cuadro en el color del texto del sistema, con la cifra en el del lienzo
   (los dos opacos: el de resalte puede ser translúcido y dejar ver el icono); el aire, en el del
   lienzo, sigue separándolo del icono. El recuento, que no llama, en el lienzo y con un filete */
@media (forced-colors: active) {
  .distintivo { forced-color-adjust: none; background: CanvasText; color: Canvas; }
  .distintivo--recuento { background: Canvas; color: CanvasText; outline: var(--trazo) solid CanvasText; outline-offset: calc(-1 * var(--trazo)); }
  .distintivo-ancla > .distintivo { outline-color: Canvas; }
}

/* ==== componentes/indicadores-carga.css ==== */
/* ==========================================================================
   Carga: la espera corta de la que no se sabe cuánto falta.
   Se dice con palabras, en una línea de estado que es región viva; un filete de 1 px
   en el color de acción se traza sobre el borde de arriba de lo que espera; y, la
   primera vez, cuando aún no hay nada que enseñar, esqueletos quietos con la forma de
   lo que llega. Nada gira ni brilla.
     <div class="carga" data-estado="carga">
       <p class="carga-texto" role="status">Cargando proyectos…</p>
       <div class="carga-zona" aria-busy="true">
         <ul class="lista-filas lista-filas--dos carga-esqueleto" aria-hidden="true">
           <li class="lista-filas-fila">
             <span class="lista-filas-delante"><span class="carga-imagen" style="--cg-ancho: 40px"></span></span>
             <span class="lista-filas-texto"><span class="carga-linea carga-linea--titulo"></span>
               <span class="carga-linea"></span></span>
           </li>
         </ul>
       </div>
     </div>
   · La línea de estado (.carga-texto) tiene su sitio siempre, 20 de alto a 12 de la zona:
     en reposo dice cuántos hay o cuándo se actualizó («4 proyectos»); mientras espera,
     qué se espera. Así, al empezar y al acabar, nada se mueve de sitio. Va fuera de la
     zona ocupada: dentro de algo con aria-busy, algunos lectores callan hasta que acaba.
   · La zona (.carga-zona) es lo que espera y lleva aria-busy="true". Su filete
     (::before) pisa el borde de arriba de lo que tiene dentro (el filete de una lista,
     de una tabla): el borde que ya estaba se enciende.
   · Esqueletos (.carga-esqueleto, con aria-hidden): .carga-linea es un renglón de texto
     con su barra centrada; la barra mide el renglón menos 12 (6 de aire arriba y abajo):
     8 en un renglón de 20 (texto de 15), con radio 2; .carga-linea--titulo, 12 en uno
     de 24 (el cuerpo), con radio 4 y 8 em de largo como mucho (un nombre); la última
     línea de un párrafo, .carga-linea--corta, se queda 6 em corta. .carga-imagen es la
     imagen: 1:1 (--cg-proporcion) con radio 4, a todo el ancho o a --cg-ancho. En
     --capa, quietos: sin brillo que corre ni latido.
   Estados en data-estado (los pone js/componentes/indicadores-carga.js):
   · "espera": los primeros 300 ms. Nada a la vista; los esqueletos guardan el sitio sin
     verse (solo con JS: sin él, nada se oculta).
   · "carga": desde 300 ms. El filete se traza de izquierda a derecha en 1.680 ms
     (--m-t7, curva --m-traza), se queda 720 (--m-t5), se apaga en 360 (--m-t4) y, tras
     260 ms vacío, se traza otra vez: acaba entero justo a los 5 s de empezar la espera.
     Un solo recorrido de 4.700 ms (5 s − 300 ms), sin bucle.
   · "sigue": desde los 5 s. El filete, entero y quieto: una raya, no un avance (a medias
     y quieto parecería progreso). La línea dice «Sigue cargando… Puede tardar un poco más».
   · .es-quieta: el filete quieto para la documentación; --cg-avance (de 0 a 1) dice
     cuánto se ha trazado.
   En el botón: .boton con aria-busy="true" (css/formas.css: el filo del principal se
   enciende) y la etiqueta cambiada («Enviando…»); aquí no hay nada suyo.
   Con «reducir movimiento», sin filete: la línea de estado y los esqueletos.
   Ningún HEX: solo roles. Funciona en claro, en oscuro, sobre la banda verde y en las islas.
   ========================================================================== */
.carga,
.carga-esqueleto {
  --cg-color: var(--accion);      /* el filete */
  --cg-grosor: var(--trazo);      /* 1 px */
  --cg-bloque: var(--capa);       /* los esqueletos */
  --cg-lado: 0px;                 /* sangría de la línea de estado (16 junto a una lista) */
  --cg-recorrido: 4700ms;         /* 5 s − 300 ms: del primer trazo a quedarse quieto */
}
.carga {
  display: grid;
  gap: var(--e-12);
  min-inline-size: 0;
  color: var(--texto);
}

/* ---- La línea de estado ---- */
.carga-texto {
  margin: 0;
  min-block-size: 20px;
  padding-inline: var(--cg-lado);
  color: var(--texto-2);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

/* ---- La zona y su filete ---- */
.carga-zona { position: relative; min-inline-size: 0; }
.carga-zona::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 1;
  display: none;
  block-size: var(--cg-grosor);
  background: var(--cg-color);
  transform-origin: left center;
  pointer-events: none;
}
.carga-zona:dir(rtl)::before { transform-origin: right center; }
.carga:is([data-estado="carga"], [data-estado="sigue"], .es-quieta) .carga-zona::before { display: block; }

/* Se traza: solo con las tres llaves (JS, sin «reducir movimiento» y el estado) */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .carga[data-estado="carga"] .carga-zona::before {
    animation: cg-trazar var(--cg-recorrido) linear both;
  }
}
/* Sigue: entero y quieto. En la documentación, quieto donde diga --cg-avance */
.carga[data-estado="sigue"] .carga-zona::before { animation: none; transform: none; }
.carga.es-quieta .carga-zona::before { animation: none; transform: scaleX(var(--cg-avance, 1)); }

/* Trazo · se queda · se apaga · vacío · trazo. Cada tramo con su curva: la del trazo
   (--m-traza) al dibujar y la del fundido (--m-suave) al apagarse. Dentro de @keyframes
   ni Chrome ni WebKit leen var() en la curva: van sus valores, los de movimiento.css.
   Los porcentajes son de 4.700 ms: 1.680 · 2.400 · 2.760 · 3.020 */
@keyframes cg-trazar {
  0% { transform: scaleX(0); opacity: 1; animation-timing-function: cubic-bezier(.45, .05, .2, 1); }
  35.745% { transform: scaleX(1); opacity: 1; animation-timing-function: linear; }
  51.064% { transform: scaleX(1); opacity: 1; animation-timing-function: cubic-bezier(.22, .61, .36, 1); }
  58.723% { transform: scaleX(1); opacity: 0; animation-timing-function: step-end; }
  64.255% { transform: scaleX(0); opacity: 1; animation-timing-function: cubic-bezier(.45, .05, .2, 1); }
  100% { transform: scaleX(1); opacity: 1; }
}

/* ---- Esqueletos ---- */
/* Espera: guardan el sitio sin verse (solo con JS) */
html[data-mov] .carga[data-estado="espera"] .carga-esqueleto { visibility: hidden; }

.carga-linea {
  --cg-renglon: 20px;
  --cg-radio: var(--r-s);
  display: flex;
  align-items: center;
  box-sizing: border-box;
  block-size: var(--cg-renglon);
  max-inline-size: 32em;
  font-size: var(--t-menor);
}
/* La barra: el renglón menos 12, centrada (6 de aire arriba y abajo) */
.carga-linea::before {
  content: "";
  flex: 1 1 auto;
  block-size: calc(var(--cg-renglon) - var(--e-12));
  border-radius: var(--cg-radio);
  background: var(--cg-bloque);
}
.carga-linea--titulo { --cg-renglon: 24px; --cg-radio: var(--r); max-inline-size: 8em; }
.carga-linea--corta { max-inline-size: 26em; margin-inline-end: 6em; }

.carga-imagen {
  display: block;
  inline-size: var(--cg-ancho, 100%);
  aspect-ratio: var(--cg-proporcion, 1);
  align-self: start;   /* WebKit: con aspect-ratio, nunca estirado */
  border-radius: var(--r);
  background: var(--cg-bloque);
}

/* Movimiento reducido: sin filete (salvo el quieto de la documentación) */
@media (prefers-reduced-motion: reduce) {
  .carga:not(.es-quieta) .carga-zona::before { display: none; }
}
@media (forced-colors: active) {
  .carga-zona::before { forced-color-adjust: none; background: Highlight; }
  .carga-linea::before, .carga-imagen { forced-color-adjust: none; background: GrayText; }
}

/* ==== componentes/indicadores-progreso.css ==== */
/* ==========================================================================
   Progreso: cuánto falta de algo que se puede medir.
   Barra: un <progress> del navegador con estilo propio (el lector lo conoce, sin JS), con su
   etiqueta encima y la cifra a la derecha.
     <div class="progreso">
       <div class="progreso-cab">
         <label class="progreso-etiqueta" for="subida">Subiendo 3 de 12 archivos</label>
         <span class="progreso-cifra" aria-hidden="true">25 %</span>
       </div>
       <progress class="progreso-barra" id="subida" max="12" value="3" aria-valuetext="25 %">25 %</progress>
       <p class="progreso-ayuda">Puedes seguir escribiendo mientras se suben.</p>
     </div>
   · Etiqueta: qué pasa y cuánto va (15/20, texto). Da nombre a la barra con for (o
     aria-labelledby). Cifra: el tanto por ciento en Haas con cifras tabulares (no baila al
     cambiar), a la derecha y a 16 como poco de la etiqueta; aria-hidden, porque el lector ya
     la oye en aria-valuetext. Ayuda (opcional): 13/16 en texto 2, a 8 de la pista.
   · Pista: 4 de alto, en línea reforzada (--linea-2), a todo el ancho y con radio 0: el radio
     empieza en 12 px; a 4 de alto, el de 2 la volvería una píldora. El tramo hecho, en el
     color de acción; crece en t3 (240 ms) con la curva de respuesta. Nunca retrocede.
   · Sin value, el <progress> no sabe cuánto falta: la pista queda vacía y quieta. Eso es
     Carga (indicadores-carga.css), no Progreso.
   Estados en data-estado (los pone js/componentes/indicadores-progreso.js):
   · "hecho": ha llegado al total. La pista llena y «100 %»; la etiqueta, en participio.
   · "error": se ha parado por un fallo. El tramo pasa al color de error y se queda donde
     estaba; la ayuda, con el icono de alerta, dice qué ha pasado y qué hacer.
   Pasos (.progreso--pasos): el título dice dónde estás y, desde M, la lista enseña el camino.
     <div class="progreso progreso--pasos">
       <p class="progreso-titulo t-titulo-3"><span class="progreso-cuenta">Paso 2 de 4</span>
         <span class="progreso-sep" aria-hidden="true"> · </span><span class="solo-lector">: </span>
         <span class="progreso-actual">Tu proyecto</span></p>
       <ol class="progreso-pasos" aria-label="Pasos del brief">
         <li class="progreso-paso" data-estado="hecho"><span class="progreso-num" aria-hidden="true">01</span>
           <span class="progreso-nombre">Tus datos<span class="solo-lector">, hecho</span></span></li>
         <li class="progreso-paso" aria-current="step">…</li>
         <li class="progreso-paso">…</li>
       </ol>
     </div>
   · Título: su tamaño lo pone quien lo usa (en un formulario, el titular del paso:
     .t-titulo-3); sin clase, 15/20. El punto medio, en texto 2. En S es lo único que se ve.
   · Cada paso: su número en la mono (12/16) dentro de un cuadro de 24 con radio 4 y filete de
     1 px; debajo, a 8, su nombre en 13/16. Los pasos reparten el ancho por igual y se unen con
     un filete de 1 px que sale a 8 de un cuadro y acaba a 8 del siguiente.
     Hecho: el cuadro lleno en el color de acción y el número en su texto; su filete, también en
     acción (lo andado). Actual (aria-current="step"): filete de acción y el nombre en 500.
     Pendiente: filete en texto 3 (3,64:1, pieza que hay que ver); el número y el nombre en
     texto 2, porque son texto y piden 4,5:1. El filete de lo que falta, en línea reforzada.
   · Hasta cinco pasos, con nombres de una o dos palabras: un nombre largo pasa a dos líneas
     bajo su cuadro, nunca debajo del siguiente.
   · Nada se pulsa: ni la barra ni los pasos. Para volver a un paso, un botón del formulario.
   Lo pone al día js/componentes/indicadores-progreso.js (WSC.progreso): valor, cifra,
   aria-valuetext, estado y pasos a la vez, y dice el final y el error una vez.
   Lectura: el filete de 1 px sobre el de la cabecera, en el color de acción, que crece al
   leer (.progreso-lectura, de la base: css/especimen.css). Aquí no hay nada suyo.
   Ningún HEX: solo roles. Funciona en claro, en oscuro, sobre la banda verde y en las islas.
   ========================================================================== */
.progreso {
  --pr-alto: 4px;                 /* la pista */
  --pr-pista: var(--linea-2);
  --pr-hecho: var(--accion);      /* el tramo hecho */
  --pr-cuadro: 24px;              /* el cuadro de un paso */
  display: grid;
  gap: var(--e-8);
  min-inline-size: 0;
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
}

/* ---- Barra: etiqueta y cifra ---- */
.progreso-cab {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--e-16);
  min-inline-size: 0;
}
.progreso-etiqueta { min-inline-size: 0; overflow-wrap: anywhere; }
.progreso-cifra {
  flex: none;
  font-variant-numeric: tabular-nums;
  text-align: end;
  white-space: nowrap;
}

/* ---- Barra: la pista y el tramo hecho (el <progress> del navegador, sin su dibujo) ---- */
.progreso-barra {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  box-sizing: border-box;
  inline-size: 100%;
  block-size: var(--pr-alto);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--pr-pista);
  color: var(--pr-hecho);
  overflow: hidden;
}
.progreso-barra::-webkit-progress-bar { background: var(--pr-pista); border-radius: 0; }
.progreso-barra::-webkit-progress-value {
  background: var(--pr-hecho);
  border-radius: 0;
  transition: width var(--m-t3) var(--m-ui), background-color var(--m-t2) var(--m-ui);
}
.progreso-barra::-moz-progress-bar { background: var(--pr-hecho); border-radius: 0; }
/* Sin value no se sabe cuánto falta: vacía y quieta (eso es Carga) */
.progreso-barra:indeterminate::-webkit-progress-value { background: transparent; }
.progreso-barra:indeterminate::-moz-progress-bar { background: transparent; animation: none; }

/* ---- Ayuda ---- */
.progreso-ayuda {
  display: flex;
  align-items: flex-start;
  gap: var(--e-8);
  margin: 0;
  color: var(--texto-2);
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
  overflow-wrap: anywhere;
}
.progreso-ayuda > .i { flex: none; }

/* ---- Error: el tramo en el color de error; la ayuda lo dice, con su icono ---- */
.progreso[data-estado="error"] { --pr-hecho: var(--error-borde); }
.progreso[data-estado="error"] .progreso-ayuda { color: var(--error-texto); }
.progreso[data-estado="error"] .progreso-ayuda > .i { color: var(--error-icono); }

/* ---- Pasos ---- */
.progreso--pasos { gap: var(--e-16); }
.progreso-titulo { margin: 0; font-variant-numeric: tabular-nums; }
.progreso-sep { color: var(--texto-2); }
.progreso-pasos {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}
.progreso-paso {
  position: relative;
  display: grid;
  align-content: start;
  justify-items: start;
  gap: var(--e-8);
  min-inline-size: 0;
  padding-inline-end: var(--e-8);
  color: var(--texto-2);
}
/* El filete hasta el siguiente: sale a 8 del cuadro y acaba a 8 del otro */
.progreso-paso:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-block-start: calc(var(--pr-cuadro) / 2 - var(--trazo));
  inset-inline: calc(var(--pr-cuadro) + var(--e-8)) var(--e-8);
  block-size: var(--trazo);
  background: var(--linea-2);
  transition: background-color var(--m-t2) var(--m-ui);
}
.progreso-num {
  display: inline-grid;
  place-items: center;
  box-sizing: border-box;
  inline-size: var(--pr-cuadro);
  block-size: var(--pr-cuadro);
  border: var(--trazo) solid var(--texto-3);
  border-radius: var(--r);
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 16px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  transition: background-color var(--m-t2) var(--m-ui), border-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.progreso-nombre {
  max-inline-size: 100%;
  font-size: var(--t-nota);
  line-height: 16px;
  letter-spacing: 0;
  overflow-wrap: break-word;
}
/* Hecho: lleno en el color de acción; su filete, lo andado */
.progreso-paso[data-estado="hecho"] { color: var(--texto); }
.progreso-paso[data-estado="hecho"] .progreso-num { background: var(--accion); border-color: var(--accion); color: var(--accion-texto); }
.progreso-paso[data-estado="hecho"]::after { background: var(--accion); }
/* Actual: filete de acción y el nombre en 500 */
.progreso-paso[aria-current="step"] { color: var(--texto); }
.progreso-paso[aria-current="step"] .progreso-num { border-color: var(--accion); }
.progreso-paso[aria-current="step"] .progreso-nombre { font-weight: var(--p-medio); }

/* En S, solo el título: «Paso 2 de 4 · Tu proyecto» */
@container pagina (max-width: 639px) {
  .progreso--pasos > .progreso-pasos { display: none; }
}

/* Movimiento reducido: el tramo y los cuadros cambian sin recorrido. Cada seudoelemento del
   navegador va en su regla: uno que no se conoce invalida la lista entera */
@media (prefers-reduced-motion: reduce) {
  .progreso-barra::-webkit-progress-value { transition: none; }
  .progreso-paso::after, .progreso-num { transition: none; }
}
@media (forced-colors: active) {
  .progreso-barra { forced-color-adjust: none; background: Canvas; outline: var(--trazo) solid CanvasText; }
  .progreso-barra::-webkit-progress-bar { background: Canvas; }
  .progreso-barra::-webkit-progress-value { background: Highlight; }
  .progreso-barra::-moz-progress-bar { background: Highlight; }
  .progreso-paso::after { forced-color-adjust: none; background: GrayText; }
  .progreso-paso[data-estado="hecho"]::after { background: Highlight; }
  .progreso-paso[data-estado="hecho"] .progreso-num { forced-color-adjust: none; background: Highlight; border-color: Highlight; color: HighlightText; }
  .progreso-paso[aria-current="step"] .progreso-num { border-color: Highlight; }
}

/* ==== componentes/menus.css ==== */
/* ==========================================================================
   Menú emergente: las acciones de una cosa, detrás de un botón.
   Un botón abre una lista de acciones en la capa superior (popover): se elige una y la
   lista se cierra. Sin JS ya funciona: el navegador abre y cierra la capa (en el centro
   de la ventana) y sus elementos se recorren con Tab. El JS (js/componentes/menus.js) la
   coloca bajo el botón y pone el teclado del botón de menú.
     <div class="menu-emergente">
       <button type="button" class="boton-icono boton-icono--contorno menu-emergente-boton"
               id="havet-boton" aria-label="Más acciones del Cliente" aria-haspopup="menu"
               aria-expanded="false" aria-controls="havet-menu" popovertarget="havet-menu">
         <svg class="i i-20" aria-hidden="true"><use href="#i-mas-opciones"/></svg>
       </button>
       <div class="menu-emergente-lista" id="havet-menu" popover role="menu" aria-labelledby="havet-boton">
         <button type="button" class="menu-emergente-elemento" role="menuitem">
           <svg class="i i-20 menu-emergente-icono" aria-hidden="true"><use href="#i-copiar"/></svg>
           <span class="menu-emergente-texto">Duplicar</span>
           <span class="menu-emergente-dato">…</span>
         </button>
         <hr class="menu-emergente-separador">
         …
       </div>
     </div>
   · El botón: el de icono de contorno («Más acciones», i-mas-opciones) o un secundario S
     con su texto y el chevron. Abierto (aria-expanded="true"): borde en el color del texto
     y fondo de capa, como el botón del menú de la cabecera.
   · Elementos (.menu-emergente-elemento): role="menuitem". Los que se marcan,
     role="menuitemcheckbox" o "menuitemradio", con aria-checked y su check
     (.menu-emergente-marca, i-check) en el sitio del icono. Grupos: role="group" con
     aria-label igual a su rótulo (.menu-emergente-rotulo, con aria-hidden: en mayúsculas,
     el lector lo deletrearía).
   · .menu-emergente-elemento--borra: el que borra, en los roles de error (texto e icono;
     encima y con foco, su fondo). Sin botón rojo: su texto dice la consecuencia.
   · data-alinear="fin" en la lista: se alinea al borde de fin del botón (el botón cierra
     una fila: tarjetas, tablas) y crece hacia dentro.
   Superficie: fondo, filete de 1 px en línea 2 y radio 4, sin sombra; de 192 a 320 de
   ancho, con 4 de relleno arriba y abajo, a 4 del botón y a 8 de los bordes de la ventana.
   Elementos de 40 de alto (44 con el dedo) a todo el ancho, con 16 a los lados y sin radio:
   el de dentro es el de fuera menos lo que lo separa (4 − 1 − 4 < 0). Icono de 20 a 12 de
   la etiqueta; el dato, a 24 como mínimo, en texto 2 y en cifras tabulares. La etiqueta
   se centra por su mayúscula, como en el botón: 2 px más de relleno abajo que arriba la
   suben 1 px, y el icono baja ese píxel (top: 1px) y queda en el centro.
   ========================================================================== */
.menu-emergente {
  --me-ancho-min: 192px;
  --me-ancho-max: 320px;
  --me-alto: 40px;
  --me-lado: var(--e-16);
  --me-hueco: var(--e-4);
  /* Solo el botón queda en la página: la lista va en la capa superior. En la
     documentación, una lista quieta cae bajo su botón, a 4, como cuando se abre */
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--me-hueco);
  vertical-align: middle;
}
.menu-emergente:has(> .menu-emergente-lista[data-alinear="fin"]) { align-items: flex-end; }

/* ---- La superficie. Sin display propio: cerrada, el navegador la quita ---- */
.menu-emergente-lista {
  box-sizing: border-box;
  inline-size: max-content;
  min-inline-size: var(--me-ancho-min);
  max-inline-size: var(--me-ancho-max);
  padding: var(--me-hueco) 0;
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
  text-align: start;
}
/* En la capa superior: nunca más ancha que la ventana (el 100 % es la ventana) y, si no
   cabe en alto, se desliza por dentro */
.menu-emergente-lista[popover] {
  max-inline-size: min(var(--me-ancho-max), 100% - 2 * var(--e-8));
  overflow-x: hidden;
  overflow-y: auto;
  overscroll-behavior: contain;
}
/* Colocada por el JS bajo el botón (o encima), en coordenadas de la ventana. Sin JS, la
   capa del navegador la centra */
.menu-emergente-lista[popover][data-colocado] {
  inset: auto;
  top: var(--me-y, 0px);
  left: var(--me-x, 0px);
  margin: 0;
  max-block-size: var(--me-alto-max, none);
}

/* ---- El elemento: toda la fila responde ---- */
.menu-emergente-elemento {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--e-12);
  box-sizing: border-box;
  inline-size: 100%;
  min-block-size: var(--me-alto);
  margin: 0;
  padding: var(--e-8) var(--me-lado) calc(var(--e-8) + 2px);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font: inherit;
  letter-spacing: inherit;
  text-align: start;
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
.menu-emergente-icono,
.menu-emergente-marca {
  position: relative;
  top: 1px;
  flex: none;
  inline-size: 20px;
  block-size: 20px;
  color: var(--texto-2);
}
.menu-emergente-texto { flex: 1 1 auto; min-inline-size: 0; }
.menu-emergente-dato {
  flex: none;
  margin-inline-start: var(--e-12);
  color: var(--texto-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
/* La marca: el check de lo elegido, en el color de acción; sin elegir, su sitio queda
   vacío para que las etiquetas no se muevan */
.menu-emergente-marca { color: var(--accion); visibility: hidden; }
.menu-emergente-elemento[aria-checked="true"] .menu-emergente-marca { visibility: visible; }

/* ---- Rótulo de grupo y separador ---- */
.menu-emergente-rotulo {
  margin: 0;
  padding: var(--e-8) var(--me-lado) var(--e-4);
  color: var(--texto-2);
  font-size: var(--t-rotulo);
  line-height: 16px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-feature-settings: "case";
}
.menu-emergente-separador {
  margin: var(--me-hueco) 0;
  border: 0;
  border-top: var(--trazo) solid var(--linea);
}

/* ---- El que borra: los roles de error, nunca un botón rojo ---- */
.menu-emergente-elemento--borra { color: var(--error-texto); }
.menu-emergente-elemento--borra .menu-emergente-icono { color: var(--error-icono); }

/* ---- Encima (con puntero), pulsado y foco. .es-encima y .es-pulsado enseñan el
   estado quieto en la documentación. Encima, fondo de capa; pulsado, un paso más (el
   de la línea) con el icono y el dato en el color del texto; en el que borra, su fondo
   de error. Con teclado, el fondo de encima y el anillo común por dentro de la fila ---- */
@media (hover: hover) {
  .menu-emergente-elemento:hover { background: var(--capa); }
  .menu-emergente-elemento--borra:hover { background: var(--error-fondo); }
}
.menu-emergente-elemento.es-encima { background: var(--capa); }
.menu-emergente-elemento--borra.es-encima { background: var(--error-fondo); }
.menu-emergente-elemento:active,
.menu-emergente-elemento.es-pulsado { background: var(--linea); }
.menu-emergente-elemento:active :is(.menu-emergente-icono, .menu-emergente-dato),
.menu-emergente-elemento.es-pulsado :is(.menu-emergente-icono, .menu-emergente-dato) { color: var(--texto); }
.menu-emergente-elemento--borra:active,
.menu-emergente-elemento--borra.es-pulsado { background: var(--error-fondo); }
.menu-emergente-elemento--borra:active .menu-emergente-icono,
.menu-emergente-elemento--borra.es-pulsado .menu-emergente-icono { color: var(--error-icono); }
.menu-emergente-elemento:focus { outline: none; }
.menu-emergente-elemento:focus-visible,
.menu-emergente-elemento.es-foco {
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: 0;
  background: var(--capa);
}
.menu-emergente-elemento--borra:focus-visible,
.menu-emergente-elemento--borra.es-foco { background: var(--error-fondo); }

/* ---- El botón abierto: borde en el color del texto y fondo de capa ---- */
.menu-emergente-boton[aria-expanded="true"] { border-color: var(--texto); background: var(--capa); }
.menu-emergente-boton.boton[aria-expanded="true"] { --b-borde: var(--texto); --b-fondo: var(--capa); }

/* Con el dedo, cada elemento mide 44 (la fila entera es la zona que responde) */
@media (any-pointer: coarse) {
  .menu-emergente { --me-alto: 44px; }
}

/* ---- Movimiento: receta contextual. Aparece en t3 con 4 px de recorrido desde el
   botón (baja si se abre debajo, sube si se abre encima) y la curva de respuesta; se va
   igual con la de salida. Sin escala. Solo con la capa del navegador ---- */
@supports selector(:popover-open) {
  @media (prefers-reduced-motion: no-preference) {
    html[data-mov] .menu-emergente-lista[popover] {
      opacity: 0;
      translate: 0 calc(-1 * var(--e-4));
      transition-property: opacity, translate, display, overlay;
      transition-duration: var(--m-t3);
      transition-timing-function: var(--m-va);
      transition-behavior: allow-discrete;
    }
    html[data-mov] .menu-emergente-lista[popover][data-lado="arriba"] { translate: 0 var(--e-4); }
    html[data-mov] .menu-emergente-lista[popover]:popover-open {
      opacity: 1;
      translate: 0 0;
      transition-timing-function: var(--m-ui);
    }
    @starting-style {
      html[data-mov] .menu-emergente-lista[popover]:popover-open { opacity: 0; translate: 0 calc(-1 * var(--e-4)); }
      html[data-mov] .menu-emergente-lista[popover][data-lado="arriba"]:popover-open { translate: 0 var(--e-4); }
    }
  }
}

/* Movimiento reducido: aparece y se va sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .menu-emergente-elemento, .menu-emergente-lista { transition: none; }
}
@media (forced-colors: active) {
  .menu-emergente-lista { border-color: CanvasText; }
  .menu-emergente-separador { border-top-color: GrayText; }
  .menu-emergente-elemento :is(.menu-emergente-marca, .menu-emergente-icono) { color: inherit; }
  .menu-emergente-elemento:is(:hover, :focus-visible, .es-encima, .es-foco) {
    forced-color-adjust: none;
    background: Highlight;
    color: HighlightText;
  }
}

/* ==== componentes/barras-herramientas.css ==== */
/* ==========================================================================
   Barra de herramientas: las acciones frecuentes de la vista, a un toque.
   Botones de icono agrupados por tema, con una sola parada de Tab: las flechas
   recorren la barra (js/componentes/barras-herramientas.js).
     <div class="barra-herramientas" role="toolbar" aria-label="Documento">
       <p class="barra-herramientas-titulo">Propuesta · Cliente.pdf</p>
       <div class="barra-herramientas-grupo" role="group" aria-label="Zoom">
         <button class="barra-herramientas-boton" type="button" aria-label="Reducir">
           <svg class="i i-20" aria-hidden="true"><use href="#i-reducir"/></svg></button>
         <output class="barra-herramientas-texto">100 %</output>
         <button class="barra-herramientas-boton" type="button" aria-label="Ampliar">…</button>
       </div>
       <div class="barra-herramientas-grupo" role="group" aria-label="Vista">
         <button class="barra-herramientas-boton" type="button" aria-label="Miniaturas"
           aria-pressed="true">…</button>
       </div>
     </div>
   · Fija (.barra-herramientas): encima de un panel o de un documento, a todo su ancho.
     56 de alto (el botón de 40 con 8 de aire) y filete inferior de 1 px en --linea: la
     medida y el filete de la cabecera.
   · Flotante (.barra-herramientas--flotante): abajo, sobre el contenido, a 16 del borde de
     abajo y del de la izquierda (en la bandera, como todo). Fondo, filete de 1 px en
     --linea-2 y radio 4; sin sombra. 4 de aire: mide 50 con su filete. Sus botones van con
     radio 0: el interior es el exterior menos lo que lo separa (4 − 1 − 4). Flota dentro de
     su vista, que lleva position: relative.
   · De selección (.barra-herramientas--seleccion): ocupa el sitio de la cabecera de una
     tabla o una lista mientras hay algo elegido; fondo --seleccion, el tinte de lo elegido.
     Encima, el botón se aclara a --fondo (la capa no se distingue del tinte).
   Partes: .barra-herramientas-grupo (role="group", con aria-label: un filete vertical de
   1 × 24 en --linea-2 lo separa del anterior, con 8 a cada lado), .barra-herramientas-boton
   (40 × 40, sin contorno, icono de 20), .barra-herramientas-titulo (lo que se ve: se corta
   con puntos suspensivos, hasta 96) y .barra-herramientas-texto (una cuenta o un valor que
   no se corta: «3 seleccionados», «100 %»).
   Estados: encima, --capa; pulsado, --capa y el icono baja 1 px; de dos estados
   (aria-pressed) activo, en el color de acción como el chip; «Más» abierto
   (aria-expanded="true"), --capa. Ningún botón se deshabilita.
   El globo: el de Globos de ayuda (globos.css y globos.js), con su espera y su colocación.
   El JS de la barra pone data-globo en cada botón con aria-label (también en «Más») y
   globos.js enseña ese nombre encima del botón, centrado y a 8 (si no cabe, debajo): a los
   500 ms con el puntero, al momento con el teclado; se va al salir, al pulsar y con Esc.
   Lo que lee el lector y lo que se ve son el mismo texto. Con el menú de «Más» abierto, no
   sale. Aquí no se dibuja: la barra no tiene globo propio.
   Lo que no cabe pasa a «Más» (.barra-herramientas-mas, lo pone el JS), de la última acción
   hacia atrás: un menú emergente (menus.css) con el icono y el nombre de cada una; la de dos
   estados, con su marca; la que lleva data-borra, en los roles de error. data-siempre deja
   un botón en la barra pase lo que pase. Sin JS, la barra pasa a dos líneas y no se pierde
   nada.
   ========================================================================== */
.barra-herramientas {
  --bh-boton: 40px;
  --bh-icono: 20px;
  --bh-hueco: var(--e-4);
  --bh-aire: var(--e-8);
  --bh-filete: 24px;
  --bh-radio: var(--r);
  --bh-encima: var(--capa);
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--bh-hueco);
  min-inline-size: 0;
  margin: 0;
  padding: var(--bh-aire);
  border: 0;
  border-block-end: var(--trazo) solid var(--linea);
  background: var(--fondo);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: -0.01em;
}
.barra-herramientas[hidden] { display: none; }
/* Con JS, una sola línea: lo que no cabe va a «Más» */
.barra-herramientas[data-bh-lista] { flex-wrap: nowrap; }

/* Grupo: los botones de un mismo tema; un filete de 1 × 24 lo separa del anterior,
   con 8 a cada lado (4 de hueco y 4 de margen) */
.barra-herramientas-grupo { display: flex; flex: none; align-items: center; gap: var(--bh-hueco); }
.barra-herramientas-grupo + .barra-herramientas-grupo { margin-inline-start: var(--e-4); }
.barra-herramientas-grupo + .barra-herramientas-grupo::before {
  content: "";
  flex: none;
  inline-size: var(--trazo);
  block-size: var(--bh-filete);
  margin-inline-end: var(--e-4);
  background: var(--linea-2);
}

/* Título: el nombre de lo que la barra tiene delante; ocupa lo que sobra y se corta */
.barra-herramientas-titulo {
  flex: 1 1 auto;
  min-inline-size: var(--e-96);
  margin: 0;
  padding-inline: var(--e-8);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Texto: una cuenta o un valor, en cifras tabulares; nunca se corta. Suelto en la barra
   ocupa lo que sobra (lleva las acciones al final); dentro de un grupo, 48 de ancho */
.barra-herramientas-texto {
  flex: none;
  margin: 0;
  padding-inline: var(--e-8);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.barra-herramientas > .barra-herramientas-texto { flex: 1 0 auto; }
.barra-herramientas-grupo > .barra-herramientas-texto { min-inline-size: var(--e-48); padding-inline: 0; text-align: center; }

/* Botón: 40 × 40, sin contorno; el icono de 20 en el centro */
.barra-herramientas-boton {
  position: relative;
  display: inline-grid;
  place-items: center;
  flex: none;
  inline-size: var(--bh-boton);
  block-size: var(--bh-boton);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--bh-radio);
  background: transparent;
  color: var(--texto);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.barra-herramientas-boton .i { inline-size: var(--bh-icono); block-size: var(--bh-icono); transition: translate var(--m-t1) var(--m-ui); }
/* De dos estados, activo: el color de acción, como el chip */
.barra-herramientas-boton[aria-pressed="true"] { background: var(--accion); color: var(--accion-texto); }
/* «Más» con su menú abierto */
.barra-herramientas-boton[aria-expanded="true"] { background: var(--bh-encima); }
/* El anillo de foco (el común) pasa por encima de los vecinos */
.barra-herramientas-boton:focus-visible { z-index: 2; }

/* Encima y pulsado. .es-encima y .es-pulsado enseñan el estado quieto en la documentación */
@media (hover: hover) {
  .barra-herramientas-boton:hover { background: var(--bh-encima); }
  .barra-herramientas-boton[aria-pressed="true"]:hover { background: var(--accion-hover); }
}
.barra-herramientas-boton.es-encima { background: var(--bh-encima); }
.barra-herramientas-boton.es-encima[aria-pressed="true"] { background: var(--accion-hover); }
.barra-herramientas-boton:active,
.barra-herramientas-boton.es-pulsado { background: var(--bh-encima); }
.barra-herramientas-boton:is(:active, .es-pulsado)[aria-pressed="true"] { background: var(--accion-pulsado); }
.barra-herramientas-boton:active .i,
.barra-herramientas-boton.es-pulsado .i { translate: 0 1px; }

/* La zona que responde: el botón y, con el dedo, 2 px más por cada lado (44 × 44, sin
   mover el dibujo) */
.barra-herramientas-boton::before { content: ""; position: absolute; inset: 0; }
@media (any-pointer: coarse) {
  .barra-herramientas-boton::before { inset: -2px; }
}

/* «Más»: el último botón. Es un menú emergente (css/componentes/menus.css): su lista va
   en la capa superior, alineada al final del botón. Lo que está fuera no se pinta */
.barra-herramientas-mas { position: relative; display: inline-grid; flex: none; }
.barra-herramientas :is(.barra-herramientas-mas, .barra-herramientas-grupo, .barra-herramientas-boton, .barra-herramientas-texto)[hidden],
.barra-herramientas [data-bh-fuera] { display: none; }

/* ---- Flotante: abajo, sobre el contenido ---- */
.barra-herramientas--flotante {
  --bh-aire: var(--e-4);
  --bh-radio: 0px;
  position: absolute;
  inset-block-end: calc(var(--e-16) + env(safe-area-inset-bottom, 0px));
  inset-inline-start: var(--e-16);
  z-index: 2;
  max-inline-size: calc(100% - 2 * var(--e-16));
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
}

/* ---- De selección: el tinte de lo elegido ---- */
.barra-herramientas--seleccion {
  --bh-encima: var(--fondo);
  background: var(--seleccion);
  border-block-end-color: var(--seleccion);
}

/* ---- Movimiento: el fondo cambia en 160 ms; con «reducir movimiento», sin transición ---- */
@media (prefers-reduced-motion: reduce) {
  .barra-herramientas-boton, .barra-herramientas-boton .i { transition: none; }
}
@media (forced-colors: active) {
  .barra-herramientas--flotante { border-color: CanvasText; }
  .barra-herramientas-grupo + .barra-herramientas-grupo::before { background: CanvasText; }
  .barra-herramientas-boton[aria-pressed="true"] { forced-color-adjust: none; background: Highlight; color: HighlightText; }
}
@media print {
  .barra-herramientas { display: none; }
}

/* ==== componentes/botones-opcion.css ==== */
/* ==========================================================================
   Botones de opción: una respuesta entre varias, todas a la vista.
   Radios nativos con el mismo name, en un fieldset con su legend: se envían en un
   formulario, funcionan sin JS y el teclado es el del navegador (Tab entra en el
   grupo, las flechas mueven y eligen). Se ven como una caja cuadrada con un
   cuadrado dentro: en el sistema no hay círculos.
     <fieldset class="boton-opcion-grupo" aria-describedby="programa-error">
       <legend class="boton-opcion-leyenda">Programa</legend>
       <p class="boton-opcion-nota">…</p>                              (opcional)
       <div class="boton-opcion-lista">
         <label class="boton-opcion">
           <input class="boton-opcion-control" type="radio" name="programa" value="esencia" required>
           <span class="boton-opcion-caja" aria-hidden="true"></span>
           <span class="boton-opcion-texto">Esencia®
             <span class="boton-opcion-ayuda">…</span></span>            (opcional)
         </label>
         …
       </div>
       <p class="boton-opcion-error" id="programa-error" hidden></p>
     </fieldset>
   · En lista (la base): una opción por línea, a 24 una de otra. Con el dedo, cada
     una responde en 44 de alto (20 + 12 + 12) sin pisar a la vecina.
   · .boton-opcion--tarjeta: toda la tarjeta elige (programas, planes). Filete de 1 px
     en línea 2 y relleno de 16; elegida, el filete pasa al color de acción. Sus
     tarjetas van en .boton-opcion-lista--tarjetas: una columna y, desde L, en fila a
     partes iguales.
   · Error (grupo obligatorio sin responder): aria-invalid="true" en cada radio (las
     cajas de la lista, o el filete de cada tarjeta, en rojo) y el mensaje en
     .boton-opcion-error, enlazado desde el fieldset con aria-describedby.
     js/componentes/botones-opcion.js lo pone (WSC.errorOpcion) y lo quita al elegir.
   Caja de 20 × 20 con radio 4 y borde de 1 px en gris de control (3,64:1). Elegida:
   el borde en el color de acción y un cuadrado de 10 × 10 a 4 px del borde, con
   radio 0 (el radio interior es el exterior menos lo que lo separa: 4 − 1 − 4 no
   llega a 0). Se distingue de la casilla: dentro no hay un visto sino un cuadrado
   lleno, y el grupo es siempre de una sola respuesta. La elegida no cambia al pasar
   por encima ni al pulsarla: pulsarla no hace nada.
   ========================================================================== */
.boton-opcion {
  --bo-lado: 20px;
  --bo-cuadrado: 10px;
  --bo-hueco: calc((var(--bo-lado) - 2 * var(--trazo) - var(--bo-cuadrado)) / 2); /* 4 */
  --bo-relleno: 0px;
  position: relative;
  display: inline-grid;
  grid-template-columns: var(--bo-lado) minmax(0, auto);
  align-items: start;
  column-gap: var(--e-12);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  font-weight: var(--p-normal);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* El radio nativo cubre la caja: recibe el clic, el foco y el teclado, y no se ve */
.boton-opcion-control {
  position: absolute;
  inset-block-start: var(--bo-relleno);
  inset-inline-start: var(--bo-relleno);
  inline-size: var(--bo-lado);
  block-size: var(--bo-lado);
  margin: 0;
  opacity: 0;
  cursor: inherit;
  z-index: 1;
}
.boton-opcion-caja {
  position: relative;
  box-sizing: border-box;
  inline-size: var(--bo-lado);
  block-size: var(--bo-lado);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: transparent;
  transition: border-color var(--m-t2) var(--m-ui);
}
/* El cuadrado: siempre está, sin color hasta que se elige (nada se escala ni se mueve) */
.boton-opcion-caja::before {
  content: "";
  position: absolute;
  inset: var(--bo-hueco);
  border-radius: 0;
  background: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
.boton-opcion-texto { display: grid; gap: var(--e-4); min-inline-size: 0; }
.boton-opcion-ayuda { font-size: var(--t-nota); line-height: 16px; font-weight: var(--p-normal); color: var(--texto-2); }

/* Elegida */
.boton-opcion-control:checked + .boton-opcion-caja { border-color: var(--accion); }
.boton-opcion-control:checked + .boton-opcion-caja::before { background: var(--accion); }

/* Encima (con puntero) y pulsado, solo en la que no está elegida: el borde pasa al
   color del texto y, mientras se pulsa, el cuadrado asoma en línea 2. .es-encima y
   .es-pulsado enseñan el estado quieto en la documentación */
@media (hover: hover) {
  .boton-opcion:hover .boton-opcion-control:not(:checked) + .boton-opcion-caja { border-color: var(--texto); }
}
.boton-opcion.es-encima .boton-opcion-control:not(:checked) + .boton-opcion-caja { border-color: var(--texto); }
.boton-opcion:active .boton-opcion-control:not(:checked) + .boton-opcion-caja,
.boton-opcion.es-pulsado .boton-opcion-control:not(:checked) + .boton-opcion-caja { border-color: var(--texto); }
.boton-opcion:active .boton-opcion-control:not(:checked) + .boton-opcion-caja::before,
.boton-opcion.es-pulsado .boton-opcion-control:not(:checked) + .boton-opcion-caja::before { background: var(--linea-2); }

/* Foco: el anillo común, en la caja (solo con teclado) */
.boton-opcion-control:focus-visible { outline: none; }
.boton-opcion.es-foco { outline: none; }
.boton-opcion-control:focus-visible + .boton-opcion-caja,
.boton-opcion.es-foco .boton-opcion-caja { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }

/* ---- Tarjeta: toda la tarjeta elige ---- */
.boton-opcion--tarjeta {
  --bo-relleno: var(--e-16);
  display: grid;
  grid-template-columns: var(--bo-lado) minmax(0, 1fr);
  padding: var(--bo-relleno);
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  transition: border-color var(--m-t2) var(--m-ui);
}
.boton-opcion--tarjeta .boton-opcion-texto { font-weight: var(--p-medio); }
.boton-opcion--tarjeta .boton-opcion-ayuda { line-height: 20px; }
.boton-opcion--tarjeta:has(> .boton-opcion-control:checked) { border-color: var(--accion); }
@media (hover: hover) {
  .boton-opcion--tarjeta:hover:has(> .boton-opcion-control:not(:checked)) { border-color: var(--texto); }
}
.boton-opcion--tarjeta.es-encima:has(> .boton-opcion-control:not(:checked)),
.boton-opcion--tarjeta:active:has(> .boton-opcion-control:not(:checked)),
.boton-opcion--tarjeta.es-pulsado:has(> .boton-opcion-control:not(:checked)) { border-color: var(--texto); }
/* En la tarjeta, el anillo abraza la tarjeta entera, no la caja */
.boton-opcion--tarjeta .boton-opcion-control:focus-visible + .boton-opcion-caja,
.boton-opcion--tarjeta.es-foco .boton-opcion-caja { outline: none; }
.boton-opcion--tarjeta:has(> .boton-opcion-control:focus-visible),
.boton-opcion--tarjeta.es-foco { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }

/* ---- El grupo: fieldset y legend sin los adornos del navegador ---- */
.boton-opcion-grupo { margin: 0; padding: 0; border: 0; min-inline-size: 0; }
/* La leyenda flota para comportarse como una etiqueta encima (y no sobre el borde) */
.boton-opcion-leyenda {
  float: left;
  inline-size: 100%;
  margin: 0 0 var(--e-12);
  padding: 0;
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  font-weight: var(--p-medio);
}
.boton-opcion-grupo > :not(.boton-opcion-leyenda) { clear: both; }
.boton-opcion-grupo:has(> .boton-opcion-nota) > .boton-opcion-leyenda { margin-block-end: var(--e-4); }
.boton-opcion-nota { margin: 0 0 var(--e-12); max-inline-size: 32em; color: var(--texto-2); font-size: var(--t-nota); line-height: 20px; }
.boton-opcion-lista { display: grid; justify-items: start; gap: var(--e-24); min-inline-size: 0; }
.boton-opcion-lista--tarjetas { justify-items: stretch; gap: var(--e-16); }
@container pagina (min-width: 1024px) {
  .boton-opcion-lista--tarjetas { grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
}

/* ---- Error del grupo: en la lista, cada caja sin elegir en rojo; en la tarjeta, su filete
   (la caja de dentro no repite el rojo). Y el mensaje debajo. Va después de encima y
   pulsado: con el puntero encima, el rojo se queda ---- */
.boton-opcion:not(.boton-opcion--tarjeta) .boton-opcion-control[aria-invalid="true"]:not(:checked) + .boton-opcion-caja { border-color: var(--error-borde); }
.boton-opcion--tarjeta:has(> .boton-opcion-control[aria-invalid="true"]:not(:checked)) { border-color: var(--error-borde); }
.boton-opcion-error {
  display: flex;
  gap: var(--e-8);
  align-items: flex-start;
  margin: var(--e-16) 0 0;
  max-inline-size: 32em;
  color: var(--error-texto);
  font-size: var(--t-nota);
  line-height: 20px;
}
.boton-opcion-error[hidden] { display: none; }
.boton-opcion-error .i { block-size: 20px; color: var(--error-icono); } /* 16 de dibujo en la línea de 20 */

/* Con el dedo, la zona que responde crece a 44 de alto sin mover nada: 12 arriba y
   12 abajo, la mitad del hueco entre opciones (la tarjeta ya mide más de 44) */
@media (any-pointer: coarse) {
  .boton-opcion:not(.boton-opcion--tarjeta)::before { content: ""; position: absolute; inset: calc(-1 * var(--e-12)) 0; }
}

/* Movimiento reducido: el color cambia al instante */
@media (prefers-reduced-motion: reduce) {
  .boton-opcion--tarjeta, .boton-opcion-caja, .boton-opcion-caja::before { transition: none; }
}
@media (forced-colors: active) {
  .boton-opcion-caja { forced-color-adjust: none; border-color: CanvasText; }
  .boton-opcion-control:checked + .boton-opcion-caja { border-color: Highlight; }
  .boton-opcion-control:checked + .boton-opcion-caja::before { background: Highlight; }
  .boton-opcion-control:focus-visible + .boton-opcion-caja { outline-color: Highlight; }
  .boton-opcion--tarjeta { border-color: CanvasText; }
  .boton-opcion--tarjeta:has(> .boton-opcion-control:checked) { border-color: Highlight; }
}

/* ==== componentes/interruptor.css ==== */
/* ==========================================================================
   Interruptor: enciende o apaga algo que surte efecto al momento.
   Una casilla nativa con role="switch" (se envía en un formulario, funciona sin JS y
   el teclado es el del navegador) que se ve como una pista con su tirador.
     <label class="interruptor">
       <input class="interruptor-control" type="checkbox" role="switch">
       <span class="interruptor-pista" aria-hidden="true"></span>
       <span class="interruptor-texto">Avisarme por correo
         <span class="interruptor-ayuda">…</span></span>
     </label>
   · .interruptor--fila: el texto a la izquierda y la pista al final (listas de ajustes).
   · Fijo (siempre encendido, no se cambia): el input lleva checked y aria-disabled="true"
     y el JS impide cambiarlo; la ayuda dice por qué.
   Pista de 40 × 24 con radio 4 y borde de 1 px; tirador de 16 × 16 a 3 px del borde, con
   radio 0 (radio interior = 4 − 1 − 3). Apagado: borde en gris de control (3,64:1) y
   tirador en texto 2. Encendido: la pista en el color de acción y el tirador en su texto
   (menta sobre verde en claro; verde sobre menta en oscuro y en la banda verde).
   ========================================================================== */
.interruptor {
  --in-ancho: 40px;
  --in-alto: 24px;
  --in-tirador: 16px;
  --in-hueco: 3px;
  --in-recorrido: calc(var(--in-ancho) - 2 * var(--trazo) - 2 * var(--in-hueco) - var(--in-tirador));
  position: relative;
  display: inline-grid;
  grid-template-columns: var(--in-ancho) minmax(0, auto);
  align-items: start;
  gap: var(--e-12);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 24px;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* El control nativo cubre la pista: recibe el clic, el foco y el teclado, y no se ve */
.interruptor-control {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: var(--in-ancho);
  block-size: var(--in-alto);
  margin: 0;
  opacity: 0;
  cursor: inherit;
  z-index: 1;
}
.interruptor-pista {
  position: relative;
  box-sizing: border-box;
  inline-size: var(--in-ancho);
  block-size: var(--in-alto);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: transparent;
  transition: background-color var(--m-t2) var(--m-ui), border-color var(--m-t2) var(--m-ui);
}
/* El tirador */
.interruptor-pista::before {
  content: "";
  position: absolute;
  inset-block-start: var(--in-hueco);
  inset-inline-start: var(--in-hueco);
  inline-size: var(--in-tirador);
  block-size: var(--in-tirador);
  border-radius: 0;
  background: var(--texto-2);
  transition: translate var(--m-t3) var(--m-ui), background-color var(--m-t2) var(--m-ui);
}
.interruptor-texto { display: grid; gap: var(--e-4); padding-block-start: 2px; line-height: 20px; min-inline-size: 0; }
.interruptor-ayuda { font-size: var(--t-nota); line-height: 16px; color: var(--texto-2); }

/* Encendido */
.interruptor-control:checked + .interruptor-pista { background: var(--accion); border-color: var(--accion); }
.interruptor-control:checked + .interruptor-pista::before { translate: var(--in-recorrido) 0; background: var(--accion-texto); }
:dir(rtl) .interruptor-control:checked + .interruptor-pista::before { translate: calc(-1 * var(--in-recorrido)) 0; }

/* Encima (con puntero) y pulsado: el borde apagado pasa al color del texto; encendido,
   la pista baja un paso de acción. Sin escalas ni sombras. .es-encima y .es-pulsado
   enseñan el estado quieto en la documentación */
@media (hover: hover) {
  .interruptor:hover .interruptor-control:not(:checked, [aria-disabled="true"]) + .interruptor-pista { border-color: var(--texto); }
  .interruptor:hover .interruptor-control:not(:checked, [aria-disabled="true"]) + .interruptor-pista::before { background: var(--texto); }
  .interruptor:hover .interruptor-control:checked:not([aria-disabled="true"]) + .interruptor-pista { background: var(--accion-hover); border-color: var(--accion-hover); }
}
.interruptor.es-encima .interruptor-control:not(:checked) + .interruptor-pista { border-color: var(--texto); }
.interruptor.es-encima .interruptor-control:not(:checked) + .interruptor-pista::before { background: var(--texto); }
.interruptor.es-encima .interruptor-control:checked + .interruptor-pista { background: var(--accion-hover); border-color: var(--accion-hover); }
.interruptor:active .interruptor-control:not([aria-disabled="true"]) + .interruptor-pista,
.interruptor.es-pulsado .interruptor-control + .interruptor-pista { border-color: var(--texto); }
.interruptor:active .interruptor-control:checked:not([aria-disabled="true"]) + .interruptor-pista,
.interruptor.es-pulsado .interruptor-control:checked + .interruptor-pista { background: var(--accion-pulsado); border-color: var(--accion-pulsado); }

/* Foco: el anillo común, en la pista (solo con teclado) */
.interruptor-control:focus-visible { outline: none; }
.interruptor-control:focus-visible + .interruptor-pista,
.interruptor.es-foco { outline: none; }
.interruptor-control:focus-visible + .interruptor-pista,
.interruptor.es-foco .interruptor-pista { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }

/* Fijo: encendido y sin cambio posible. Capa y texto terciario, nunca opacidad */
.interruptor:has(.interruptor-control[aria-disabled="true"]) { cursor: default; }
.interruptor-control[aria-disabled="true"] + .interruptor-pista { background: var(--capa); border-color: var(--capa); }
.interruptor-control[aria-disabled="true"] + .interruptor-pista::before { background: var(--texto-3); }

/* En fila: el texto a la izquierda y la pista al final, a todo el ancho (ajustes, cookies) */
.interruptor--fila {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--in-ancho);
  inline-size: 100%;
}
.interruptor--fila .interruptor-texto { grid-column: 1; grid-row: 1; }
.interruptor--fila .interruptor-pista { grid-column: 2; grid-row: 1; }
.interruptor--fila .interruptor-control { inset-inline-start: auto; inset-inline-end: 0; }

/* Con el dedo, la zona que responde crece a 44 de alto (la pista mide 24) sin mover nada */
@media (any-pointer: coarse) {
  .interruptor-control { inset-block-start: -10px; block-size: calc(var(--in-alto) + 20px); }
}

/* Movimiento reducido: el tirador salta, sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .interruptor-pista, .interruptor-pista::before { transition: none; }
}
@media (forced-colors: active) {
  .interruptor-pista { border-color: CanvasText; }
  .interruptor-pista::before { background: CanvasText; }
  .interruptor-control:checked + .interruptor-pista { background: Highlight; border-color: Highlight; }
  .interruptor-control:checked + .interruptor-pista::before { background: HighlightText; }
}

/* ==== componentes/chips.css ==== */
/* ==========================================================================
   Chips: filtran lo que se ve, eligen una respuesta, guardan un valor escrito o
   proponen una frase. La pieza es el .chip de formas.css (32 px de alto, borde de 1,
   radio 4, texto de 13 a 12 del borde; activo, en el color de acción). Aquí van el
   grupo, sus cuatro tipos y dos variantes del grupo.
   · Filtro: varios a la vez. Botones con aria-pressed en un grupo con nombre. El que
     lleva data-chip-todos («Todos») se enciende solo cuando no queda otro (chips.js).
       <div class="chip-grupo" role="group" aria-label="Filtrar proyectos por servicio">
         <button type="button" class="chip" aria-pressed="true" data-chip-todos>Todos</button>
         <button type="button" class="chip" aria-pressed="false">Naming</button>
       </div>
   · Elección: uno. Radios nativos con cara de chip: funcionan sin JS, el teclado es el
     del navegador (flechas) y se envían con el formulario.
       <label class="chip-opcion"><input class="chip-control" type="radio" name="plazo"
         value="cuanto-antes"><span class="chip">Cuanto antes</span></label>
   · Entrada: un valor escrito que se quita con su aspa. Van en .chip-campo, con el campo
     donde se escriben (Intro añade lo escrito: chips.js).
       <div class="chip-campo">
         <span class="chip chip--entrada"><span class="chip-texto">Branding</span>
           <button type="button" class="chip-quitar" aria-label="Quitar Branding">
             <svg class="i i-16" aria-hidden="true"><use href="#i-cerrar"/></svg></button></span>
         <input class="chip-campo-entrada" id="servicios" type="text">
       </div>
   · Sugerencia: botones sin estado en un grupo con data-chip-rellena="id del campo":
     escriben su texto en ese campo y se ocultan mientras tiene texto. Sin JS no hacen
     nada, así que el grupo va con hidden y lo enseña chips.js.
   · .chip-grupo--formulario: 40 de alto y texto de 15, la medida del botón S, para los
     formularios (la del chip de Formularios).
   · .chip-grupo--desliza: en S, una sola fila que se desliza; el lado que esconde chips
     se funde en 24 px (data-mas, chips.js), como las pestañas de la herramienta.
   Estados en ARIA: aria-pressed (filtro), :checked (elección); encima, pulsado y foco,
   también quietos para la documentación (.es-encima, .es-pulsado, .es-foco).
   Por qué:
   · Dentro de un grupo o de un campo, el borde va en gris de control (--linea-control,
     3,64:1 en claro, 4,79 en oscuro, 4,28 sobre verde), el de los campos y el botón
     secundario: el de la base (--linea-2, 1,60:1) no llega al 3:1 de lo que hay que ver
     para saber que se pulsa. Con :where, el encima y el activo de la base siguen mandando.
   · El aspa: 24 × 24 a 3 px del borde, con radio 0 (4 − 1 − 3), como el tirador del
     interruptor; su icono, cerrar de 16. Con el foco en el aspa, el anillo abraza el chip:
     alrededor del aspa no cabe sin salirse.
   · El campo de entrada mide 48, como los demás: 7 arriba y abajo centran una fila de
     chips de 32 ((48 − 2 − 32) / 2); 8 a los lados.
   · La fila que se desliza guarda 6 px de aire arriba y abajo ((44 − 32) / 2): ahí caben
     la zona de 44 del dedo y el anillo del foco (2 a 3 px) sin que el recorte los corte.
   ========================================================================== */
.chip {
  --ch-quitar: 24px;
  --ch-hueco: 3px;
}

/* ---- Grupo: salta de línea; 8 entre chips ---- */
.chip-grupo {
  --ch-alto: 32px;
  --ch-sangrado: 0px;   /* lo que la fila que se desliza entra en el margen del contenedor */
  --ch-fundido: var(--e-24);
  --ch-aire: 6px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e-8);
  margin: 0;
  padding: 0;
  list-style: none;
  min-inline-size: 0;
}
.chip-grupo[hidden] { display: none; }
/* Con el dedo, entre líneas 12: las zonas de 44 se tocan sin pisarse. En la fila que se
   desliza, 8 de aire arriba y abajo: la zona del chip (6 por lado) cabe entera dentro del
   recorte del desplazamiento, también cuando el navegador ajusta el borde al píxel */
@media (any-pointer: coarse) {
  .chip-grupo { row-gap: var(--e-12); --ch-aire: 8px; }
}
/* Dentro del grupo y del campo, el borde de un control; el texto, en una línea y centrado
   por su mayúscula, como en el botón: 2 px de relleno abajo la suben 1 px (sin ellos cae
   0,7 px por debajo del centro; con ellos queda 0,3 por encima, un píxel entero) */
:where(.chip-grupo, .chip-campo) .chip { border-color: var(--linea-control); white-space: nowrap; padding-bottom: 2px; }

/* ---- Encima y pulsado ----
   Apagado: encima, el borde en el color del texto (la base); pulsado, además, un
   relleno de capa. Encendido: la acción baja un paso encima y otro al pulsar. */
@media (hover: hover) {
  .chip[aria-pressed="true"]:hover,
  .chip-opcion:hover .chip-control:checked + .chip { background: var(--accion-hover); border-color: var(--accion-hover); }
  .chip-opcion:hover .chip-control:not(:checked) + .chip { border-color: var(--texto); }
}
.chip.es-encima:not([aria-pressed="true"]) { border-color: var(--texto); }
.chip.es-encima[aria-pressed="true"] { background: var(--accion-hover); border-color: var(--accion-hover); }
:is(button, a).chip:not([aria-pressed="true"]):active,
.chip-opcion:active .chip-control:not(:checked) + .chip,
.chip.es-pulsado:not([aria-pressed="true"]) { background: var(--capa); border-color: var(--texto); }
:is(button, a).chip[aria-pressed="true"]:active,
.chip-opcion:active .chip-control:checked + .chip,
.chip.es-pulsado[aria-pressed="true"] { background: var(--accion-pulsado); border-color: var(--accion-pulsado); }

/* ---- Elección: el radio nativo cubre la cara del chip ---- */
.chip-opcion {
  position: relative;
  display: inline-flex;
  min-inline-size: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.chip-control {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  opacity: 0;
  cursor: inherit;
  z-index: 1;
}
.chip-control:checked + .chip { background: var(--accion); border-color: var(--accion); color: var(--accion-texto); }
/* Foco: el anillo común, en la cara (solo con teclado) */
.chip-control:focus-visible { outline: none; }
.chip-control:focus-visible + .chip { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }

/* ---- En un formulario: la medida del botón S, y la etiqueta centrada por su mayúscula
   como en él (2 px de relleno abajo) ---- */
.chip-grupo--formulario { --ch-alto: var(--alto-s); }
.chip-grupo--formulario .chip {
  height: var(--ch-alto);
  padding: 0 var(--e-16) 2px;
  font-size: 15px;
  line-height: 20px;
}

/* ---- Entrada: el valor y su aspa ---- */
.chip--entrada {
  gap: var(--e-4);
  max-inline-size: 100%;
  padding-block-end: 0;
  padding-inline-end: var(--ch-hueco);
  cursor: default;
}
/* El chip de entrada no se pulsa: encima no cambia (solo su aspa) */
@media (hover: hover) {
  :where(.chip-grupo, .chip-campo) .chip--entrada:hover { border-color: var(--linea-control); }
}
/* El aspa se queda a 3 px arriba y abajo; el que sube es el texto */
.chip-texto { min-inline-size: 0; padding-block-end: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.chip-quitar {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--ch-quitar);
  block-size: var(--ch-quitar);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto-2);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .chip-quitar:hover { background: var(--capa); color: var(--texto); }
}
.chip-quitar:active, .chip-quitar.es-encima, .chip-quitar.es-pulsado { background: var(--capa); color: var(--texto); }
/* Con el foco en el aspa, el anillo común alrededor del chip y el aspa en su tono de encima.
   Sin :has, el anillo va en el aspa (el foco común) */
.chip--entrada:has(.chip-quitar:focus-visible) { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }
.chip--entrada:has(.chip-quitar:focus-visible) .chip-quitar { outline: none; background: var(--capa); color: var(--texto); }

/* ---- Campo de entrada: los chips y, detrás, donde se escribe. Mide y responde como un campo ---- */
.chip-campo {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--e-8);
  min-block-size: var(--alto-m);
  padding: 7px var(--e-8);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  cursor: text;
  transition: border-color var(--m-t2) var(--m-ui);
}
@media (hover: hover) {
  .chip-campo:hover { border-color: var(--texto); }
}
.chip-campo:has(.chip-campo-entrada:focus-visible) { border-color: var(--foco); outline: var(--trazo-foco) solid var(--foco); outline-offset: 0; }
.chip-campo:has(.chip-campo-entrada[aria-invalid="true"]) { border-color: var(--error-borde); }
/* Lo que se escribe: 16 px (en el iPhone, sin zoom al tocar), sin borde propio; el texto
   queda a 16 del borde del campo, como en los demás (8 + 8) */
.chip-campo .chip-campo-entrada {
  flex: 1 1 8em;
  min-inline-size: 8em;
  inline-size: auto;
  block-size: 32px;   /* el alto del chip: la línea de chips y la de escribir, iguales */
  min-block-size: 0;
  margin: 0;
  padding: 0 var(--e-8);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font: inherit;
  font-size: 16px;
  line-height: 24px;
}
.chip-campo .chip-campo-entrada::placeholder { color: var(--texto-2); }
@supports selector(:has(*)) {
  .chip-campo .chip-campo-entrada:focus-visible { outline: none; }
}

/* ---- Fila que se desliza (S): una línea; el aire de arriba y abajo deja sitio al dedo y
   al foco, y el de los lados, al foco del primero y del último ---- */
@container pagina (max-width: 639px) {
  .chip-grupo--desliza {
    --ch-lado: max(var(--ch-sangrado), var(--ch-aire));
    flex-wrap: nowrap;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
    margin: calc(-1 * var(--ch-aire)) calc(-1 * var(--ch-lado));
    padding: var(--ch-aire) var(--ch-lado);
    /* Lo que llega con Tab se queda fuera del fundido */
    scroll-padding-inline: calc(var(--ch-lado) + var(--ch-fundido));
  }
  .chip-grupo--desliza::-webkit-scrollbar { display: none; }
  .chip-grupo--desliza > * { flex: none; }
}
/* El lado que esconde chips se funde: sin sombra ni flecha. La máscara solo lee el alfa */
.chip-grupo--desliza[data-mas] {
  --ch-fi: 0px;
  --ch-fd: 0px;
  -webkit-mask-image: linear-gradient(to right, transparent, black var(--ch-fi), black calc(100% - var(--ch-fd)), transparent);
  mask-image: linear-gradient(to right, transparent, black var(--ch-fi), black calc(100% - var(--ch-fd)), transparent);
}
.chip-grupo--desliza[data-mas~="izq"] { --ch-fi: calc(var(--ch-lado, 0px) + var(--ch-fundido)); }
.chip-grupo--desliza[data-mas~="der"] { --ch-fd: calc(var(--ch-lado, 0px) + var(--ch-fundido)); }

/* ---- Con el dedo: 44 de zona sin mover el dibujo ----
   El botón del filtro y de la sugerencia ya la tiene (formas.css). La elección, en el radio
   que cubre la cara; el aspa, en un pseudoelemento de 44 × 44 centrado en ella */
@media (any-pointer: coarse) {
  .chip-control { inset-block: min(0px, calc((var(--ch-alto, 32px) - 44px) / 2)); block-size: auto; }
  .chip-quitar::after { content: ""; position: absolute; inset: calc((var(--ch-quitar) - 44px) / 2); }
}

/* Movimiento reducido: el color cambia al instante */
@media (prefers-reduced-motion: reduce) {
  .chip, .chip-quitar, .chip-campo { transition: none; }
}
@media (forced-colors: active) {
  :where(.chip-grupo, .chip-campo) .chip, .chip-campo { border-color: CanvasText; }
  .chip[aria-pressed="true"], .chip-control:checked + .chip {
    forced-color-adjust: none;
    background: Highlight;
    border-color: Highlight;
    color: HighlightText;
  }
  .chip-quitar { color: CanvasText; }
}

/* ==== componentes/selector-fecha.css ==== */
/* ==========================================================================
   Fecha: un día o un periodo, escrito (dd/mm/aaaa) o elegido en un calendario.
   Dos piezas. El campo es el del sistema (.campo de formas.css: etiqueta en 15/20 y 500,
   ayuda en 13/20 y texto 2, todo a 8) con la fecha y su botón en una caja; funciona solo:
   sin JS es un campo de texto que se envía en un formulario. El calendario (.calendario)
   lo pone el JS: bajo el campo, en la capa superior (popover) y sin atrapar el foco, o a
   la vista, dentro de la página.
     <div class="campo selector-fecha" data-min="hoy">
       <label for="inicio">Fecha de inicio del proyecto</label>
       <p class="ayuda" id="inicio-ayuda">dd/mm/aaaa</p>
       <div class="selector-fecha-control">
         <input class="selector-fecha-campo" id="inicio" name="inicio" type="text"
                inputmode="numeric" autocomplete="off" maxlength="10"
                aria-describedby="inicio-ayuda inicio-error">
         <button class="selector-fecha-boton" type="button" aria-label="Elegir en el calendario" hidden>
           <svg class="i i-20" aria-hidden="true"><use href="#i-calendario"/></svg></button>
       </div>
       <p class="selector-fecha-error" id="inicio-error" hidden></p>
     </div>
   · .selector-fecha--intervalo (role="group", con .selector-fecha-etiqueta): un periodo.
     Dos campos, Desde y Hasta (.selector-fecha-extremo), y un solo calendario que marca el
     primer día, el último y el tramo entre los dos.
   · .selector-fecha--vista: el calendario va siempre abierto bajo el campo (reservar).
   · Días no disponibles: data-min y data-max («hoy» o aaaa-mm-dd) y data-sin (lun … dom y
     fechas aaaa-mm-dd), con su motivo. En texto 3 y tachados; el motivo va en su nombre.
   Caja: 48 de alto, borde de 1 px en gris de control (3,64:1) y radio 4. La fecha, a 16
   (el iPhone no amplía) y con cifras tabulares. Su ancho es el de la fecha: 16 de relleno,
   «00/00/0000» (84,94 px en la Haas de 16) con 2 px para el cursor y 8 hasta el botón.
   El botón, como el de Campos de texto: 40 × 40 a 3 px del borde, radio 0 (4 − 1 − 3),
   el icono en texto 2 y, encima, capa; su anillo de foco va por dentro (por fuera
   chocaría con el borde de la caja).
   Calendario: siete columnas de 40 (44 con el dedo) y 16 de relleno: 314 de ancho. Días
   de 40 × 40 con radio 4. Hoy, un filete de 1 px en el color del texto. Elegido, el color
   de acción con su texto. El tramo de un periodo, en --seleccion y a la altura del día.
   Encima y pulsado, un velo del color del texto (8 y 16 %): vale igual sobre el fondo y
   sobre el tramo, donde la capa sería un hueco más claro. En S, el calendario del campo
   ocupa el ancho de la pantalla, con el margen de la página dentro: su rejilla cae en las
   columnas del texto.
   ========================================================================== */
.selector-fecha {
  --sf-cifras: calc(5.3125em + 2px);
  --sf-boton: 40px;
  --sf-hueco: 3px;
  justify-items: start;
  min-inline-size: 0;
  margin: 0;
  padding: 0;
  border: 0;
  color: var(--texto);
}
/* El título de un periodo (el grupo no lleva label): como la etiqueta del campo */
.selector-fecha-etiqueta {
  margin: 0;
  font-size: var(--t-menor);
  line-height: 20px;
  font-weight: var(--p-medio);
}
.selector-fecha > .ayuda { margin: 0; }
/* La caja: la fecha y su botón */
.selector-fecha-control {
  display: flex;
  align-items: center;
  box-sizing: border-box;
  max-inline-size: 100%;
  block-size: var(--alto-m);
  padding-inline-end: var(--sf-hueco);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  cursor: text;
  transition: border-color var(--m-t2) var(--m-ui);
}
.selector-fecha-control > .selector-fecha-campo {
  box-sizing: content-box;
  flex: none;
  inline-size: var(--sf-cifras);
  min-inline-size: 0;
  block-size: calc(var(--alto-m) - 2 * var(--trazo));
  min-block-size: 0;
  margin: 0;
  padding: 0 var(--e-8) 0 var(--e-16);
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font: inherit;
  font-size: 16px;
  line-height: 24px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  -webkit-tap-highlight-color: transparent;
}
.selector-fecha-control > .selector-fecha-campo:focus-visible { outline: none; }
/* El botón del calendario (lo enseña el JS: sin él no hay calendario) */
.selector-fecha-boton {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  inline-size: var(--sf-boton);
  block-size: var(--sf-boton);
  margin: 0 0 0 auto;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto-2);
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.selector-fecha-boton[hidden] { display: none; }
.selector-fecha-boton .i { width: 20px; height: 20px; }
/* Abierto: el botón se queda en capa mientras el calendario está a la vista */
.selector-fecha-boton[aria-expanded="true"] { background: var(--capa); color: var(--texto); }
/* Error: una línea bajo la caja, con su icono; el borde, en el color de error */
.selector-fecha-error {
  display: flex;
  gap: var(--e-8);
  align-items: flex-start;
  max-inline-size: 32em;
  margin: 0;
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--error-texto);
}
.selector-fecha-error[hidden] { display: none; }
/* 16 de dibujo, centrado en la línea de 20 */
.selector-fecha-error .i { flex: none; width: 16px; height: 16px; margin-block: 2px; color: var(--error-icono); }

/* Encima (con puntero) y pulsado. .es-encima y .es-pulsado enseñan el estado quieto */
@media (hover: hover) {
  .selector-fecha-control:hover { border-color: var(--texto); }
  .selector-fecha-boton:hover { background: var(--capa); color: var(--texto); }
}
.selector-fecha-control.es-encima { border-color: var(--texto); }
.selector-fecha-boton.es-encima { background: var(--capa); color: var(--texto); }
.selector-fecha-boton:active, .selector-fecha-boton.es-pulsado { background: var(--linea); color: var(--texto); }

/* Foco. En la fecha, el anillo común rodea la caja pegado al borde (como todo campo: ahí
   se escribe); en el botón, va por dentro */
.selector-fecha-control:has(> .selector-fecha-campo:focus-visible),
.selector-fecha-control.es-foco {
  border-color: var(--foco);
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: 0;
}
.selector-fecha-boton:focus-visible, .selector-fecha-boton.es-foco {
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo-foco));
  border-radius: 0;
}
/* Error, por encima de encima y del foco */
.selector-fecha-control:has(> .selector-fecha-campo[aria-invalid="true"]) { border-color: var(--error-borde); }

/* Periodo: dos campos (Desde, Hasta), uno junto a otro si caben; si no, uno sobre otro */
.selector-fecha-extremos { display: flex; flex-wrap: wrap; gap: var(--e-16) var(--e-24); }
.selector-fecha-extremo { display: grid; gap: var(--e-4); justify-items: start; min-inline-size: 0; }
.selector-fecha-subetiqueta { font-size: var(--t-nota); line-height: 20px; color: var(--texto); }

/* ---- Calendario ---- */
.calendario {
  --cal-dia: 40px;
  --cal-relleno: var(--e-16);
  --cal-sep: var(--e-8);
  --cal-encima: color-mix(in srgb, var(--texto) 8%, transparent);
  --cal-pulsado: color-mix(in srgb, var(--texto) 16%, transparent);
  box-sizing: border-box;
  inline-size: calc(7 * var(--cal-dia) + 2 * var(--cal-relleno) + 2 * var(--trazo));
  max-inline-size: 100%;
  margin: 0;
  padding: var(--cal-relleno);
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  font-size: var(--t-menor);
  line-height: 20px;
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.calendario:empty { display: none; }
/* Bajo el campo, en la capa superior: el JS da la posición (--cal-x, --cal-y) */
.calendario[popover] {
  position: fixed;
  inset: auto;
  top: var(--cal-y, 0px);
  left: var(--cal-x, 0px);
  max-inline-size: none;
  overflow: visible;
}

/* Cabecera: el mes y el año en las cinco primeras columnas; anterior y siguiente, en las
   del sábado y el domingo */
.calendario-cab {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
}
.calendario-mes {
  margin: 0;
  font-size: var(--t-menor);
  line-height: 20px;
  font-weight: var(--p-medio);
  white-space: nowrap;
}
.calendario-paso {
  position: relative;
  display: grid;
  place-items: center;
  justify-self: center;
  box-sizing: border-box;
  inline-size: min(var(--cal-dia), 100%);
  aspect-ratio: 1;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--r);
  background: transparent;
  color: var(--texto);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background-color var(--m-t2) var(--m-ui);
}
.calendario-paso .i { width: 20px; height: 20px; }

/* La rejilla: una tabla con role="grid"; siete columnas iguales */
.calendario-rejilla {
  inline-size: 100%;
  margin: 0;
  border-collapse: collapse;
  border-spacing: 0;
  table-layout: fixed;
}
.calendario-rejilla th {
  block-size: 32px;
  padding: 0;
  font-size: var(--t-rotulo);
  line-height: 16px;
  font-weight: var(--p-normal);
  letter-spacing: 0;
  text-align: center;
  vertical-align: middle;
  color: var(--texto-2);
}
.calendario-rejilla td {
  padding: 0;
  text-align: center;
  vertical-align: middle;
  -webkit-tap-highlight-color: transparent;
}
.calendario-rejilla td:focus-visible { outline: none; }
.calendario-rejilla td[data-fecha] { cursor: pointer; }
.calendario-rejilla td[aria-disabled="true"] { cursor: default; }
/* El día: un cuadro de 40 (44 con el dedo); en S, si la columna es más estrecha, su ancho */
.calendario-dia {
  position: relative;
  display: grid;
  place-items: center;
  box-sizing: border-box;
  inline-size: min(var(--cal-dia), 100%);
  aspect-ratio: 1;
  margin-inline: auto;
  border-radius: var(--r);
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
/* Hoy: filete de 1 px en el color del texto */
.calendario-rejilla td[aria-current="date"] > .calendario-dia::after {
  content: "";
  position: absolute;
  inset: 0;
  border: var(--trazo) solid var(--texto);
  border-radius: var(--r);
  pointer-events: none;
}
/* No disponible: texto 3 y tachado (no solo el color); el motivo va en su nombre */
.calendario-rejilla td[aria-disabled="true"] > .calendario-dia {
  color: var(--texto-3);
  text-decoration: line-through;
  text-decoration-thickness: var(--trazo);
}
/* El tramo de un periodo, a la altura del día: del centro del primero al del último */
.calendario-rejilla td[data-tramo] {
  background: linear-gradient(var(--seleccion), var(--seleccion)) center / 100% 100% no-repeat;
}
.calendario-rejilla td[data-tramo="inicio"] { background-position: right center; background-size: 50% 100%; }
.calendario-rejilla td[data-tramo="fin"] { background-position: left center; background-size: 50% 100%; }
:dir(rtl) .calendario-rejilla td[data-tramo="inicio"] { background-position: left center; }
:dir(rtl) .calendario-rejilla td[data-tramo="fin"] { background-position: right center; }
/* Elegido (el día, o el primero y el último de un periodo): el color de acción y su texto.
   Si además es hoy, el filete pasa dentro, a 2 px, en el texto de acción (radio 4 − 2) */
.calendario-rejilla td[aria-selected="true"]:not([data-tramo="medio"]) > .calendario-dia {
  background: var(--accion);
  color: var(--accion-texto);
}
.calendario-rejilla td[aria-current="date"][aria-selected="true"]:not([data-tramo="medio"]) > .calendario-dia::after {
  inset: 2px;
  border-color: var(--accion-texto);
  border-radius: var(--r-s);
}

/* Encima y pulsado: el velo sobre el día; el elegido baja un paso de acción, como el
   interruptor. .es-encima y .es-pulsado enseñan el estado quieto */
@media (hover: hover) {
  .calendario-paso:hover { background: var(--cal-encima); }
  .calendario-rejilla td[data-fecha]:not([aria-disabled="true"]):hover > .calendario-dia { background: var(--cal-encima); }
  .calendario-rejilla td[aria-selected="true"]:not([data-tramo="medio"]):hover > .calendario-dia { background: var(--accion-hover); }
}
.calendario-paso.es-encima { background: var(--cal-encima); }
.calendario-rejilla td.es-encima > .calendario-dia { background: var(--cal-encima); }
.calendario-rejilla td.es-encima[aria-selected="true"]:not([data-tramo="medio"]) > .calendario-dia { background: var(--accion-hover); }
.calendario-paso:active, .calendario-paso.es-pulsado { background: var(--cal-pulsado); }
.calendario-rejilla td[data-fecha]:not([aria-disabled="true"]):active > .calendario-dia,
.calendario-rejilla td.es-pulsado > .calendario-dia { background: var(--cal-pulsado); }
.calendario-rejilla td[aria-selected="true"]:not([data-tramo="medio"], [aria-disabled="true"]):active > .calendario-dia,
.calendario-rejilla td.es-pulsado[aria-selected="true"]:not([data-tramo="medio"]) > .calendario-dia { background: var(--accion-pulsado); }

/* Foco: el anillo común alrededor del día (solo con teclado); por encima de los vecinos */
.calendario-rejilla td:focus-visible, .calendario-rejilla td.es-foco { position: relative; z-index: 1; }
.calendario-rejilla td:focus-visible > .calendario-dia,
.calendario-rejilla td.es-foco > .calendario-dia {
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: var(--foco-sep);
}
.calendario-paso:focus-visible { outline-offset: calc(-1 * var(--trazo-foco)); }

/* La nota: la regla de los días que no se pueden elegir, o lo que falta por hacer */
.calendario-pie {
  margin: var(--e-12) 0 0;
  padding-block-start: var(--e-12);
  border-block-start: var(--trazo) solid var(--linea);
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--texto-2);
  text-wrap: balance;
}
.calendario-pie:empty { display: none; }
/* Lo que solo oye el lector (el nombre de cada día de la semana; los meses al pasar, lo
   que falta y los errores, en la región viva del campo): fuera de la vista */
.calendario-lector, .selector-fecha-voz {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* S. El calendario del campo ocupa el ancho de la pantalla, sin bordes a los lados, y su
   relleno es el margen de la página. A la vista, sin marco: la rejilla llena la columna */
@container pagina (max-width: 639px) {
  .calendario[popover] {
    left: 0;
    right: 0;
    inline-size: auto;
    padding-inline: max(var(--margen, 24px), env(safe-area-inset-left, 0px)) max(var(--margen, 24px), env(safe-area-inset-right, 0px));
    border-inline: 0;
    border-radius: 0;
  }
  .selector-fecha--vista > .calendario {
    inline-size: 100%;
    padding: 0;
    border: 0;
    border-radius: 0;
    background: transparent;
  }
}

/* Con el dedo: cada día y cada paso miden 44 × 44; el botón de la caja responde en 44
   sin moverse (dibuja 40) */
@media (any-pointer: coarse) {
  .calendario { --cal-dia: 44px; }
  .selector-fecha-boton::after { content: ""; position: absolute; inset: -2px; }
}

/* Receta contextual: el calendario baja 4 px y aparece en 240 ms (sube, si se abre encima
   del campo). Una animación que se lanza al abrir: en reposo no queda nada por animar, y
   sin JS o con «reducir movimiento» aparece sin más */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .calendario[popover]:popover-open { animation: cal-llega var(--m-t3) var(--m-ui) backwards; }
  html[data-mov] .calendario[popover][data-lado="arriba"]:popover-open { animation-name: cal-llega-arriba; }
}
@keyframes cal-llega { from { opacity: 0; translate: 0 calc(-1 * var(--e-4)); } }
@keyframes cal-llega-arriba { from { opacity: 0; translate: 0 var(--e-4); } }
@media (prefers-reduced-motion: reduce) {
  .selector-fecha-control, .selector-fecha-boton, .calendario-paso, .calendario-dia { transition: none; }
}
@media (forced-colors: active) {
  .selector-fecha-control, .calendario { border-color: CanvasText; }
  .calendario-rejilla td[aria-selected="true"] > .calendario-dia { forced-color-adjust: none; background: Highlight; color: HighlightText; }
  .calendario-rejilla td[aria-disabled="true"] > .calendario-dia { color: GrayText; }
  .calendario-rejilla td[aria-current="date"] > .calendario-dia::after { border-color: CanvasText; }
  .calendario-rejilla td:focus-visible > .calendario-dia { outline-color: Highlight; }
}
@media print {
  .selector-fecha-boton, .calendario[popover] { display: none; }
}

/* ==== componentes/campos.css ==== */
/* ==========================================================================
   Campos de texto: recogen lo que solo sabe quien escribe (un nombre, un email, un mensaje).
   La base es .campo de formas.css (etiqueta encima en 15/20 y 500, control de 48 con borde
   de 1 px en gris de control y radio 4, texto de 16 para que el móvil no amplíe la página,
   .ayuda en 13/20 y texto 2, todo a 8). Este fichero no la cambia: le añade partes.
     <div class="campo">
       <label for="web">Web <span class="campo-opcional">(opcional)</span></label>
       <p class="ayuda" id="web-ayuda">Si aún no tienes web, déjalo en blanco.</p>
       <input id="web" type="text" inputmode="url" autocomplete="url" aria-describedby="web-ayuda">
       <p class="campo-error" id="web-error"><svg class="i i-16">…</svg><span>…</span></p>
     </div>
   · Error: aria-invalid="true" en el control y .campo-error debajo, enlazado con
     aria-describedby: borde rojo, icono y una frase. Nunca solo color.
   · Área de texto: textarea (120 de alto mínimo) y, si hay tope, .campo-contador[data-max]
     debajo: «120 de 500». No se usa maxlength: corta lo pegado sin avisar. Si sobra, el
     contador pasa a rojo con su icono (data-estado="exceso") y lo escrito se queda.
   · .campo-caja: envuelve el input cuando lleva algo más dentro; el borde, el fondo y los
     estados pasan del input a la caja. Dentro, en este orden:
       .campo-prefijo o .campo-icono · input · .campo-sufijo o .campo-boton
     Prefijo y sufijo (+34, €, https://): texto 2, a 16 del borde y a 12 del filete de 1 px
     en línea 2 que los separa de lo escrito. Son otra etiqueta del mismo input (label for):
     se pulsan y el lector los lee con el nombre («Teléfono +34»). Icono: 20, a 16 del borde
     y a 12 del texto; no se lee (aria-hidden).
     Botón (ver la contraseña, data-campo-ver con aria-pressed): 40 × 40 a 3 px del borde,
     con radio 0 (radio interior = 4 − 1 − 3), como el tirador del interruptor.
   · Solo lectura: readonly. Se enfoca, se selecciona y se copia; el lector lo lee en su
     sitio. Caja en capa, sin borde a la vista y con el texto en su color. Las muestras
     quietas de la documentación son readonly con tabindex="-1" (no se enfocan): no se
     pintan como solo lectura. Deshabilitado no existe: no se enfoca ni se copia, y el
     lector se lo salta.
   Foco: en un campo de texto se ve siempre (ahí se escribe): borde en foco y el anillo
   común de 2 px pegado al borde. Encima: borde en el color del texto. .es-encima y .es-foco
   enseñan el estado quieto en la documentación.
   ========================================================================== */
.campo {
  --ca-lado: var(--e-16);          /* del borde al texto, al prefijo o al icono */
  --ca-hueco: var(--e-12);         /* del prefijo, el filete o el icono al texto */
  --ca-boton: 40px;                /* el botón de dentro: 48 − 2 × 1 − 2 × 3 */
  --ca-boton-sep: 3px;             /* del botón al borde: el del tirador del interruptor */
}

/* La ayuda de dentro, en texto 2 (5,62:1) también donde el navegador la aclara */
.campo :is(input, textarea)::placeholder { opacity: 1; }

/* Lo opcional se dice; lo obligatorio, no (sin asteriscos) */
.campo-opcional { font-weight: var(--p-normal); color: var(--texto-2); }

/* ---- Error: debajo del campo, icono y frase ---- */
.campo :is(input, textarea)[aria-invalid="true"] { border-color: var(--error-borde); }
.campo-error {
  display: flex;
  gap: var(--e-8);
  align-items: flex-start;
  margin: 0;
  max-inline-size: 32em;
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--error-texto);
}
/* 16 de dibujo, centrado en la línea de 20 */
.campo-error .i { flex: none; margin-block: 2px; color: var(--error-icono); }

/* ---- Contador del área de texto ---- */
.campo-contador {
  display: flex;
  gap: var(--e-8);
  align-items: flex-start;
  margin: 0;
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--texto-2);
  font-variant-numeric: tabular-nums;
}
.campo-contador .i { display: none; flex: none; margin-block: 2px; color: var(--error-icono); }
.campo-contador[data-estado="exceso"] { color: var(--error-texto); }
.campo-contador[data-estado="exceso"] .i { display: block; }

/* ---- Encima y foco quietos (documentación) ---- */
.campo :is(input, textarea).es-encima { border-color: var(--texto); }
.campo :is(input, textarea).es-foco { outline-offset: 0; border-color: var(--foco); }

/* ---- Solo lectura ---- */
.campo :is(input, textarea)[readonly]:not([tabindex="-1"]) { background: var(--capa); border-color: var(--capa); }

/* ---- La caja: el input con prefijo, sufijo, icono o botón ---- */
.campo-caja {
  display: flex;
  align-items: stretch;
  min-inline-size: 0;
  min-block-size: var(--alto-m);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  cursor: text;
  transition: border-color var(--m-t2) var(--m-ui), background-color var(--m-t2) var(--m-ui);
}
/* El input deja su borde y su fondo a la caja y ocupa lo que queda */
.campo .campo-caja > input {
  flex: 1 1 0%;
  inline-size: auto;
  min-inline-size: 0;
  min-block-size: calc(var(--alto-m) - 2 * var(--trazo));
  border: 0;
  border-radius: 0;
  background: transparent;
}
.campo .campo-caja > input:focus-visible { outline: none; }
.campo-prefijo, .campo-sufijo {
  display: flex;
  flex: none;
  align-items: center;
  color: var(--texto-2);
  font-size: 16px;
  line-height: 24px;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.campo-prefijo { padding-inline: var(--ca-lado) var(--ca-hueco); border-inline-end: var(--trazo) solid var(--linea-2); }
.campo-sufijo { padding-inline: var(--ca-hueco) var(--ca-lado); border-inline-start: var(--trazo) solid var(--linea-2); }
.campo-icono { flex: none; align-self: center; margin-inline-start: var(--ca-lado); color: var(--texto-2); pointer-events: none; }
.campo .campo-caja > :is(.campo-prefijo, .campo-icono) + input { padding-inline-start: var(--ca-hueco); }
.campo .campo-caja > input:has(+ .campo-sufijo) { padding-inline-end: var(--ca-hueco); }
.campo .campo-caja > input:has(+ .campo-boton) { padding-inline-end: var(--e-8); }

/* El botón de dentro (ver la contraseña): sin borde; encima, capa y el icono en texto */
.campo-boton {
  position: relative;
  display: grid;
  flex: none;
  place-items: center;
  align-self: center;
  inline-size: var(--ca-boton);
  block-size: var(--ca-boton);
  margin: 0;
  margin-inline-end: var(--ca-boton-sep);
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto-2);
  font: inherit;
  cursor: pointer;
  transition: background-color var(--m-t2) var(--m-ui), color var(--m-t2) var(--m-ui);
}
.campo-boton .i { grid-area: 1 / 1; }
/* Apagado, el ojo (ver); pulsado, el ojo tachado (ocultar): la forma dice el estado */
.campo-boton[aria-pressed="true"] .campo-ver,
.campo-boton:not([aria-pressed="true"]) .campo-ocultar { display: none; }
/* El anillo de foco, por dentro del botón: no toca el borde de la caja */
.campo-boton:focus-visible,
.campo-boton.es-foco { outline: var(--trazo-foco) solid var(--foco); outline-offset: calc(-1 * var(--trazo-foco)); border-radius: 0; }

/* Encima (con puntero) y pulsado. .es-encima y .es-pulsado, quietos */
@media (hover: hover) {
  .campo-caja:hover { border-color: var(--texto); }
  .campo-boton:hover { background: var(--capa); color: var(--texto); }
}
.campo-caja.es-encima { border-color: var(--texto); }
.campo-boton.es-encima { background: var(--capa); color: var(--texto); }
.campo-boton:active, .campo-boton.es-pulsado { background: var(--linea); color: var(--texto); }

/* Foco en la caja cuando se escribe dentro (el botón lleva el suyo) */
.campo-caja:has(> input:focus-visible),
.campo-caja.es-foco { outline: var(--trazo-foco) solid var(--foco); outline-offset: 0; border-color: var(--foco); }

/* Error y solo lectura en la caja, por encima de encima y del foco */
.campo .campo-caja:has(> [aria-invalid="true"]) { border-color: var(--error-borde); }
.campo-caja:has(> input[readonly]:not([tabindex="-1"])) { background: var(--capa); border-color: var(--capa); cursor: default; }

/* Con el dedo: el botón responde en 44 × 44 sin moverse (dibuja 40) */
@media (any-pointer: coarse) {
  .campo-boton::after { content: ""; position: absolute; inset: -2px; }
}

@media (prefers-reduced-motion: reduce) {
  .campo-caja, .campo-boton { transition: none; }
}
@media (forced-colors: active) {
  .campo-caja { border-color: CanvasText; }
  .campo-caja:has(> input:focus-visible) { outline-color: Highlight; border-color: Highlight; }
  .campo-prefijo, .campo-sufijo { border-color: GrayText; }
  .campo-boton { color: ButtonText; }
  .campo-boton:focus-visible { outline-color: Highlight; }
  .campo :is(input, textarea)[readonly]:not([tabindex="-1"]),
  .campo-caja:has(> input[readonly]:not([tabindex="-1"])) { border-color: GrayText; }
}

/* ==== componentes/busqueda.css ==== */
/* ==========================================================================
   Búsqueda: encuentra en un conjunto (proyectos, servicios, artículos) mientras se escribe.
   Un formulario de búsqueda con un campo que es un combobox: las sugerencias salen en una
   lista (listbox) agrupada, con la coincidencia resaltada y la ruta de cada una. El dibujo y
   el teclado son los del buscador de la herramienta.
     <form class="busqueda" role="search" action="/buscar" data-busqueda-atajo>
       <div class="busqueda-campo">
         <svg class="i i-20" aria-hidden="true"><use href="#i-buscar"/></svg>
         <input class="busqueda-q" type="search" name="q" role="combobox" aria-label="Buscar en wsc.design"
           aria-expanded="false" aria-autocomplete="list" autocomplete="off" placeholder="Proyectos, servicios, artículos">
         <kbd class="busqueda-tecla" aria-hidden="true">/</kbd>
         <button type="button" class="busqueda-borrar" aria-label="Borrar la búsqueda">i-cerrar</button>
       </div>
       <div class="busqueda-panel" hidden><div class="busqueda-lista" role="listbox" aria-label="Sugerencias"></div></div>
       <script type="application/json" class="busqueda-datos">{ grupos, items, inicio, sugerencias }</script>
     </form>
   Sin JS es un formulario que va a la página de resultados (action). Con JS
   (js/componentes/busqueda.js) pinta la lista: el total, los grupos (.busqueda-grupo con su
   rótulo y su cuenta), las sugerencias (.busqueda-op: título y ruta, la coincidencia en <mark>)
   y, sin resultados, «Nada para «…». Prueba con:» y consultas que sí dan algo
   (.busqueda-op--consulta, con la lupa delante).
   · .busqueda--s: 40 de alto, para una barra (la cabecera de un producto); con el dedo, 44, como
     los mandos de la cabecera de la herramienta. La de serie mide 48, como el campo de texto y
     el botón M.
   · En S, al tocar el campo, la búsqueda pasa a su propia capa a pantalla completa
     (<dialog class="busqueda-capa">, modal): el campo arriba, con «Cerrar», y la lista debajo.
     Lo hace el JS; en M y L la lista cuelga del campo, a 4 de él.
   Estados en ARIA: aria-expanded en el campo, aria-selected en la sugerencia activa
   (aria-activedescendant: el foco no sale del campo), aria-busy en la lista mientras busca.
   Campo: borde de 1 en gris de control (3,64:1, el del campo de texto) y radio 4; la lupa y la
   tecla «/» en texto 2; el texto a 16 (Safari del iPhone no acerca la página). Con el foco,
   borde y anillo de 2 en el color de foco por dentro, como el buscador de la herramienta.
   Borrar: el icono de cerrar en un botón de alto − 8 (40; 32 en la barra) que cierra la línea a
   12 del borde, como la lupa la abre; su radio es 0 (4 − 1 − 3: está a 3 del borde interior).
   Solo aparece con texto (:placeholder-shown: el campo lleva placeholder). La tecla «/» es la
   pista del atajo: solo sin foco, sin texto y con teclado a mano (con el dedo no se ve).
   Lista: lo que flota se separa con filete de 1 en línea 2 y radio 4; sin sombra. Cada
   sugerencia mide 44 como poco; la activa, en capa con el anillo de foco por dentro, como el
   resultado con foco del buscador de la herramienta. La coincidencia, en el rol de selección y
   sin radio, como la selección de texto.
   ========================================================================== */
.busqueda {
  --bu-alto: 48px;                              /* M: el del campo de texto y el botón M */
  --bu-boton: calc(var(--bu-alto) - 8px);       /* borrar: 40; 32 en la barra */
  --bu-hueco: var(--e-4);                       /* entre el campo y la lista */
  --bu-lista: 480px;                            /* alto máximo de la lista: 10 × 48 */
  --bu-fin: calc((var(--bu-boton) - 20px) / 2); /* aire del icono dentro del botón de borrar */
  position: relative;
  display: block;
  inline-size: 100%;
  min-inline-size: 0;
  margin: 0;
  color: var(--texto);
}
.busqueda--s { --bu-alto: 40px; }

/* ---- El campo ---- */
.busqueda-campo {
  display: flex;
  align-items: center;
  gap: var(--e-8);
  box-sizing: border-box;
  block-size: var(--bu-alto);
  padding-inline: var(--e-12) calc(var(--e-12) - var(--bu-fin));
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto-2);
  cursor: text;
  transition: border-color var(--m-t2) var(--m-ui);
}
.busqueda-campo > .i { flex: none; }
.busqueda-q {
  flex: 1;
  min-inline-size: 0;
  block-size: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--texto);
  font: inherit;
  font-size: 16px; /* 16 o más: Safari del iPhone no acerca la página al escribir */
  line-height: 24px;
  letter-spacing: -0.01em;
  text-overflow: ellipsis;
  -webkit-appearance: none;
  appearance: none;
}
.busqueda-q:focus, .busqueda-q:focus-visible { outline: none; }
.busqueda-q::placeholder { color: var(--texto-2); opacity: 1; }
.busqueda-q::-webkit-search-cancel-button,
.busqueda-q::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }

/* La tecla del atajo: una tecla como las de la documentación (mono 12, caja de 1 en línea 2) */
.busqueda-tecla {
  flex: none;
  box-sizing: border-box;
  min-inline-size: 24px;
  margin-inline-end: var(--bu-fin);
  padding: 0 var(--e-4);
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r-s);
  color: var(--texto-2);
  font-family: var(--f-mono);
  font-size: 12px;
  line-height: 20px;
  letter-spacing: 0;
  text-align: center;
  white-space: nowrap;
}
/* Borrar: el icono de cerrar; solo con texto */
.busqueda-borrar {
  position: relative;
  flex: none;
  display: inline-grid;
  place-items: center;
  inline-size: var(--bu-boton);
  block-size: var(--bu-boton);
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: none;
  color: var(--texto-2);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: color var(--m-t2) var(--m-ui);
}
.busqueda-borrar:focus-visible { border-radius: 0; outline-offset: calc(-1 * var(--trazo-foco)); }
.busqueda-q:placeholder-shown ~ .busqueda-borrar,
.busqueda-q:not(:placeholder-shown) ~ .busqueda-tecla,
.busqueda-campo:focus-within .busqueda-tecla { display: none; }

/* Encima (con puntero) y pulsado. .es-encima y .es-pulsado enseñan el estado quieto */
@media (hover: hover) {
  .busqueda-campo:hover { border-color: var(--texto); }
  .busqueda-borrar:hover { color: var(--texto); }
  .busqueda-op:hover { background: var(--capa); }
}
.busqueda-campo.es-encima { border-color: var(--texto); }
.busqueda-borrar:active, .busqueda-borrar.es-pulsado { color: var(--texto); }
.busqueda-op.es-encima { background: var(--capa); }
.busqueda-op:active, .busqueda-op.es-pulsado { background: var(--capa); }

/* Foco: el campo entero, con borde y anillo de 2 por dentro (el foco no sale del campo mientras
   se recorre la lista). .es-foco, en el campo, lo enseña quieto */
.busqueda-campo:has(.busqueda-q:focus),
.busqueda-campo.es-foco {
  border-color: var(--foco);
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo));
}
.busqueda-campo.es-foco .busqueda-tecla { display: none; }

/* ---- La lista ---- */
.busqueda-panel {
  position: absolute;
  z-index: 10;
  inset-block-start: calc(100% + var(--bu-hueco));
  inset-inline: 0;
  box-sizing: border-box;
  max-block-size: var(--bu-lista);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-block: var(--e-8);
  border: var(--trazo) solid var(--linea-2);
  border-radius: var(--r);
  background: var(--fondo);
  color: var(--texto);
  text-align: start;
}
.busqueda-panel[hidden] { display: none; }
/* En la documentación, la lista quieta va en el flujo, bajo su campo */
.busqueda-panel.es-quieta { position: relative; inset: auto; margin-block-start: var(--bu-hueco); }
.busqueda-total, .busqueda-nada { margin: 0; color: var(--texto-2); }
.busqueda-total { padding: var(--e-8) var(--e-16) 0; font-size: var(--t-nota); line-height: 16px; }
.busqueda-nada { padding: var(--e-8) var(--e-16); font-size: var(--t-menor); line-height: 20px; letter-spacing: -0.01em; }
.busqueda-grupo + .busqueda-grupo { margin-block-start: var(--e-8); border-top: var(--trazo) solid var(--linea); }
.busqueda-grupo-t {
  display: flex;
  justify-content: space-between;
  gap: var(--e-16);
  margin: 0;
  padding: var(--e-12) var(--e-16) var(--e-4);
  color: var(--texto-2);
  font-size: var(--t-rotulo);
  line-height: 16px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-feature-settings: "case";
}
.busqueda-cuenta { letter-spacing: 0; font-variant-numeric: tabular-nums; }
.busqueda-op {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: center;
  column-gap: var(--e-12);
  box-sizing: border-box;
  min-block-size: 44px;
  padding: var(--e-8) var(--e-16);
  color: var(--texto);
  text-decoration: none;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.busqueda-op-t { font-size: var(--t-menor); line-height: 20px; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.busqueda-op-r { color: var(--texto-2); font-size: var(--t-nota); line-height: 16px; overflow-wrap: anywhere; }
/* La coincidencia, del alto de su línea (en línea, el fondo de <mark> pisaría la de al lado) */
.busqueda-op mark { display: inline-block; padding: 0; border-radius: 0; background: var(--seleccion); color: inherit; }
/* Una consulta (sin resultados, «Prueba con»): la lupa delante dice que busca, no que abre */
.busqueda-op--consulta { grid-template-columns: 20px minmax(0, 1fr); }
.busqueda-op--consulta > .i { grid-row: 1 / span 2; align-self: center; color: var(--texto-2); }
.busqueda-op--consulta > :not(.i) { grid-column: 2; }
.busqueda-op-r { font-variant-numeric: tabular-nums; }
/* La activa (flechas): capa y el anillo de foco por dentro */
.busqueda-op[aria-selected="true"] {
  background: var(--capa);
  outline: var(--trazo-foco) solid var(--foco);
  outline-offset: calc(-1 * var(--trazo-foco));
}
.busqueda-lista[aria-busy="true"] { cursor: progress; }

/* ---- En S: su propia capa, a pantalla completa ---- */
.busqueda-capa {
  --bu-alto: 40px;
  --bu-boton: 32px;
  --bu-fin: 6px;
  position: fixed;
  inset: 0;
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 100%;
  max-inline-size: none;
  max-block-size: none;
  margin: 0;
  padding: 0;
  border: 0;
  background: var(--fondo);
  color: var(--texto);
  flex-direction: column;
}
.busqueda-capa[open], .busqueda-capa.es-quieta { display: flex; }
.busqueda-capa::backdrop { background: var(--velo); }
.busqueda-capa-barra {
  display: flex;
  align-items: center;
  gap: var(--e-8);
  flex: none;
  padding: calc(var(--e-8) + env(safe-area-inset-top, 0px)) max(var(--e-16), env(safe-area-inset-right, 0px)) var(--e-8) max(var(--e-16), env(safe-area-inset-left, 0px));
  border-bottom: var(--trazo) solid var(--linea);
}
.busqueda-capa-barra > .busqueda-campo { flex: 1; min-inline-size: 0; }
.busqueda-capa-barra > .busqueda-cerrar { flex: none; }
.busqueda-capa .busqueda-panel {
  position: static;
  flex: 1;
  min-block-size: 0;
  max-block-size: none;
  margin: 0;
  padding-block: var(--e-8) calc(var(--e-16) + env(safe-area-inset-bottom, 0px));
  padding-inline: env(safe-area-inset-left, 0px) env(safe-area-inset-right, 0px);
  border: 0;
  border-radius: 0;
}

/* La lista entra como lo contextual: 4 px y fundido en t3, con la curva de respuesta. En la
   capa, quieta: la capa ya es el cambio */
@media (prefers-reduced-motion: no-preference) {
  html[data-mov] .busqueda-panel:not(.es-quieta) {
    transition: opacity var(--m-t3) var(--m-ui), translate var(--m-t3) var(--m-ui);
  }
  html[data-mov] .busqueda-capa .busqueda-panel { transition: none; }
  @starting-style {
    html[data-mov] .busqueda-panel:not(.es-quieta) { opacity: 0; translate: 0 calc(-1 * var(--e-4)); }
    html[data-mov] .busqueda-capa .busqueda-panel { opacity: 1; translate: none; }
  }
}

/* Con el dedo: el borrar responde en 44 sin crecer; la búsqueda de barra y la de la capa miden
   44, como los mandos de la cabecera; la tecla no se ve (no hay teclado) */
@media (any-pointer: coarse) {
  .busqueda--s { --bu-alto: 44px; }
  .busqueda-borrar::after { content: ""; position: absolute; inset: min(0px, calc((var(--bu-boton) - 44px) / 2)); }
  .busqueda-capa { --bu-alto: 44px; --bu-boton: 36px; --bu-fin: 8px; }
}
@media (hover: none) {
  .busqueda-tecla { display: none; }
}

/* Movimiento reducido: la lista aparece sin recorrido */
@media (prefers-reduced-motion: reduce) {
  .busqueda-campo, .busqueda-borrar, .busqueda-panel { transition: none; }
}
@media (forced-colors: active) {
  .busqueda-campo { border-color: CanvasText; }
  .busqueda-campo:has(.busqueda-q:focus) { outline-color: Highlight; }
  .busqueda-panel { border-color: CanvasText; }
  .busqueda-op[aria-selected="true"] { outline-color: Highlight; }
  .busqueda-op mark { forced-color-adjust: none; background: Mark; color: MarkText; }
}

/* ==== componentes/casillas.css ==== */
/* ==========================================================================
   Casilla: marca lo que se acepta o se elige en un formulario, y vale al enviarlo.
   Una casilla nativa (se envía con el formulario, funciona sin JS y el teclado es el del
   navegador) que se ve como una caja dibujada por el sistema, igual en todos los navegadores.
     <label class="casilla">
       <input class="casilla-control" type="checkbox" name="whatsapp">
       <span class="casilla-caja" aria-hidden="true">
         <svg class="i i-16 casilla-marca"><use href="#i-check"/></svg></span>
       <span class="casilla-texto">Prefiero que me escribáis por WhatsApp</span>
     </label>
   · Con ayuda o error: .casilla-campo envuelve la etiqueta y, fuera de ella y bajo el texto,
     .casilla-ayuda (puede llevar un enlace) y .casilla-error (i-alerta y el texto), enlazadas
     con aria-describedby. Con error, aria-invalid="true" en el control; sin él, .casilla-error
     queda vacía y con hidden.
   · En grupo: fieldset.casilla-grupo con su legend, si hace falta una .casilla-ayuda, y la
     lista ul.casilla-lista. .casilla-lista--sangrada mete una lista bajo el texto de «Todos».
   · Marcar todo: el control lleva data-casilla-todo y aria-controls con los id de las que
     manda; la caja, un segundo icono, .casilla-parcial (i-menos), que se ve cuando está a
     medias (:indeterminate). Lo gobierna js/componentes/casillas.js.
   · Sin texto a la vista (la fila de una tabla): solo la caja, y aria-label en el control.
   Caja de 20 × 20 con radio 4 y borde de 1 px en gris de control (3,64:1) sobre el fondo.
   Marcada o a medias, el color de acción y el icono de 16 en su texto: menta sobre verde en
   claro; verde sobre menta en oscuro y en la banda verde. 20 es la línea de la etiqueta
   (15/20): la caja y la primera línea empiezan y acaban juntas.
   La base de formas.css vale tal cual: .casilla es la fila (flex, 12 entre caja y texto,
   15/20) y su input, la casilla del navegador teñida con accent-color. Con .casilla-caja, el
   input se hace invisible encima de la caja y la caja la dibuja el sistema.
   ========================================================================== */
.casilla, .casilla-campo, .casilla-grupo, .casilla-lista {
  --ca-caja: 20px;
  /* Lo que va bajo el texto (ayuda, error, la lista de «Todos»): caja + 12 */
  --ca-sangria: calc(var(--ca-caja) + var(--e-12));
}
/* Con caja dibujada, la fila mide lo que su texto (el blanco de la derecha no responde) y
   es la referencia del control, que así se desliza con ella dentro de un panel o una tabla */
.casilla:has(> .casilla-caja) {
  position: relative;
  inline-size: fit-content;
  max-inline-size: 100%;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
/* El control nativo, invisible, encima de la caja: recibe el clic, el foco y el teclado.
   Sin inset se queda donde estaría en la fila, en su principio: donde está la caja */
.casilla-control {
  position: absolute;
  inline-size: var(--ca-caja);
  block-size: var(--ca-caja);
  margin: 0;
  opacity: 0;
  cursor: inherit;
}
.casilla-control:focus-visible { outline: none; }
.casilla-caja {
  display: grid;
  place-items: center;
  flex: none;
  box-sizing: border-box;
  inline-size: var(--ca-caja);
  block-size: var(--ca-caja);
  border: var(--trazo) solid var(--linea-control);
  border-radius: var(--r);
  background: var(--fondo);
  transition: background-color var(--m-t2) var(--m-ui), border-color var(--m-t2) var(--m-ui);
}
/* El check y el guion, uno encima del otro; solo se ve el del estado */
.casilla-caja > .i { grid-area: 1 / 1; visibility: hidden; }

/* Marcada y a medias: la caja en el color de acción; el icono, en su texto */
.casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion); border-color: var(--accion); }
.casilla-control:checked:not(:indeterminate) + .casilla-caja > .casilla-marca,
.casilla-control:indeterminate + .casilla-caja > .casilla-parcial { visibility: visible; color: var(--accion-texto); }

/* Encima (con puntero) y pulsada: sin marcar, el borde pasa al color del texto; marcada,
   la caja baja un paso de acción. .es-encima y .es-pulsado enseñan el estado quieto */
@media (hover: hover) {
  .casilla:hover > .casilla-control:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--texto); }
  .casilla:hover > .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion-hover); border-color: var(--accion-hover); }
}
.casilla.es-encima > .casilla-control:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--texto); }
.casilla.es-encima > .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion-hover); border-color: var(--accion-hover); }
.casilla:active > .casilla-control:not(:checked, :indeterminate) + .casilla-caja,
.casilla.es-pulsado > .casilla-control:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--texto); }
.casilla:active > .casilla-control:is(:checked, :indeterminate) + .casilla-caja,
.casilla.es-pulsado > .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: var(--accion-pulsado); border-color: var(--accion-pulsado); }

/* Error: el borde, en el rojo de error (gana a encima y pulsada); el texto va debajo */
.casilla > .casilla-control[aria-invalid="true"]:not(:checked, :indeterminate) + .casilla-caja { border-color: var(--error-borde); }

/* Foco: el anillo común, en la caja (solo con teclado) */
.casilla.es-foco { outline: none; }
.casilla-control:focus-visible + .casilla-caja,
.casilla.es-foco > .casilla-caja { outline: var(--trazo-foco) solid var(--foco); outline-offset: var(--foco-sep); }

/* Ayuda y error: fuera de la etiqueta (la ayuda puede llevar un enlace) y bajo el texto */
.casilla-ayuda {
  margin: var(--e-4) 0 0;
  padding-inline-start: var(--ca-sangria);
  max-inline-size: calc(var(--medida) + var(--ca-sangria));
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--texto-2);
}
.casilla-error {
  display: flex;
  gap: var(--e-8);
  align-items: flex-start;
  margin: var(--e-8) 0 0;
  padding-inline-start: var(--ca-sangria);
  max-inline-size: calc(var(--medida) + var(--ca-sangria));
  font-size: var(--t-nota);
  line-height: 20px;
  color: var(--error-texto);
}
.casilla-error[hidden] { display: none; }
/* El icono de 16, centrado en la línea de 20 */
.casilla-error > .i { block-size: 20px; color: var(--error-icono); }

/* Grupo: la leyenda hace la pregunta, como la etiqueta de un campo; la lista, debajo */
.casilla-grupo { margin: 0; padding: 0; border: 0; min-inline-size: 0; }
.casilla-grupo > legend {
  padding: 0;
  font-size: var(--t-menor);
  line-height: 20px;
  font-weight: var(--p-medio);
  color: var(--texto);
}
.casilla-grupo > .casilla-ayuda { padding-inline-start: 0; max-inline-size: var(--medida); }
.casilla-grupo > .casilla-lista { margin-block-start: var(--e-12); }
/* Una debajo de otra, a 24: la caja (20) y la separación hacen 44, la zona de cada fila */
.casilla-lista { display: grid; gap: var(--e-24); margin: 0; padding: 0; list-style: none; }
.casilla-lista > li { min-inline-size: 0; }
.casilla-lista > li > .casilla-lista { margin-block-start: var(--e-24); }
.casilla-lista--sangrada { padding-inline-start: var(--ca-sangria); }

/* Con el dedo, la etiqueta responde en 44 de alto (y de ancho, si es solo la caja) sin mover
   nada: el pseudoelemento de la base (formas.css) crece de 40 a 44. El enlace de la ayuda
   responde en 44 con otro, centrado en él; la etiqueta gana a la zona del enlace de debajo */
@media (any-pointer: coarse) {
  .casilla:has(> .casilla-caja)::before { inset: -12px min(0px, calc(50% - 22px)); }
  .casilla:has(> .casilla-caja) > .casilla-texto { position: relative; z-index: 1; }
  .casilla-ayuda a:not([class]) { position: relative; white-space: nowrap; }
  .casilla-ayuda a:not([class])::before { content: ""; position: absolute; inset: 0; z-index: 1; }
  .casilla-ayuda a:not([class])::after { content: ""; position: absolute; inset: calc(50% - 22px) min(0px, calc(50% - 22px)); }
}

/* Movimiento reducido: el color cambia sin transición */
@media (prefers-reduced-motion: reduce) {
  .casilla-caja { transition: none; }
}
@media (forced-colors: active) {
  .casilla-caja { border-color: CanvasText; background: Canvas; }
  .casilla-control:is(:checked, :indeterminate) + .casilla-caja { background: Highlight; border-color: Highlight; }
  .casilla-control:checked:not(:indeterminate) + .casilla-caja > .casilla-marca,
  .casilla-control:indeterminate + .casilla-caja > .casilla-parcial { color: HighlightText; }
}

