/*
 * Bifrost 3 — Catálogo de piezas del sistema Bifrost-3 (chore 90.060 · U-1).
 *
 * FUENTE DEL DISEÑO: desde la decisión T20 del 2026-09-15, el sistema de Hugin
 * que fija docs/bifrost-diseno-contrato.md; el catálogo lo viste desde las chores
 * 90.254 a 90.261, y el texto vigente de sus convenios está en
 * docs/bifrost3-convenciones.md. Hasta entonces la fuente fue la maqueta
 * Bifrost-3 (54 pantallas, doble tema), versión validada del 2026-08-14, §2
 * «Sistema Bifrost-3 — el catálogo que se lleva tal cual». Hoy es ARCHIVO
 * (docs/maqueta/README.md): de ella siguen la FORMA de las pantallas y los
 * NOMBRES de las piezas, no su vestido.
 * Esa versión (chore 90.111) dibuja además la GESTIÓN de las nueve familias, y
 * las piezas que estrena ya están aquí (chore 90.117): buscador de personas
 * (.bf-pick), campo congelado (.bf-input.frozen), pozo de pegado (.bf-paste),
 * modo reordenar (.grip/.updown/.moving/.bf-ordbar), selección de tabla
 * (th/td.ck + tr.sel), casilla de tres estados (.bf-box.eff/.mix), columna de
 * acciones (td.acts) y cabecera de expediente (.bf-rechead). La correspondencia
 * convenio F → pieza está en docs/bifrost3-convenciones.md, y que no falte
 * ninguna lo vigila CatalogoDePiezasTests contra el marcado de la maqueta.
 * Ninguna pantalla de gestión debería volver a escribir
 * a mano lo que ya tiene nombre aquí: el marcado suelto es cómo se llegó a las
 * catorce tablas distintas que saldó la 90.063.
 * Las piezas de abajo conservan los nombres de clase de la maqueta, de modo que
 * el marcado de una pantalla maquetada se pega en una vista y se viste solo —con
 * el sistema de hoy, así que no se verá como la maqueta—. Las variables son los
 * tokens --bf-* de tokens.css (allí eran locales: --surface, --ink…), que es lo
 * que el white-label sobreescribe y lo que atraviesa el Shadow DOM del shell.
 *
 * CONVENIOS (M-1…M-13, escritos en la cabecera de tokens.css y reescritos al
 * sistema de Hugin en la chore 90.274). Los que se ven aquí a cada paso:
 *   · Sin sombras en las SUPERFICIES (M-4): la elevación es contraste. Lo que
 *     FLOTA es cristal con su caída y su bisel, lo que se arrastra lleva la de lo
 *     elevado y el diálogo es macizo con la suya. Siguen el foco y los halos.
 *   · Una superficie por tarjeta (M-5): cabeceras y pies se separan SÓLO con
 *     filete: --bf-border-strong, el separador, que desde la 90.146 ya no es el
 *     mismo token que el filete de un control. El relleno hundido (--bf-panel-bg)
 *     es para piezas interiores rodeadas de superficie: chips, módulos del
 *     árbol, pies de modal/drawer.
 *   · 60/30/10 (M-3), con dos naranjas y dos trabajos (M-1): --bf-accent es la
 *     marca y el «aquí estás», la acción primaria va en --bf-cta con tinta oscura;
 *     verde/ámbar/rojo son estado y nunca van solos (LED + texto, M-2).
 *   · Filete de campo (M-12): todo control accionable lleva --bf-field-line, más
 *     presente que el --bf-border de las superficies y CALIBRADO a >=3:1 contra
 *     la superficie donde se apoya (SC 1.4.11, chore 90.146). Un separador NO es
 *     un control: ese lleva --bf-border-strong.
 *     EXCEPCIÓN (chore 90.399): los CAMPOS y los DESPLEGABLES van SIN CANTO, como
 *     en Hugin, que quita ese filete a sabiendas: el velo dice «aquí se escribe» y
 *     la flecha dice «aquí se elige». El borde se apaga (--bf-field-edge
 *     transparente), no se quita, y sólo se enciende en foco y en error.
 *   · El LED se centra con la PRIMERA LÍNEA de su bloque (M-10).
 *
 * RESPONSIVE (convenios MF-1…MF-8, chore 90.096; el texto vigente, en
 * docs/bifrost3-convenciones.md): el sistema manda a ancho completo y los
 * convenios MF por debajo. El contrato de
 * cortes (620/900/1100), la regla mobile-first y la métrica táctil están junto
 * a los tokens, en tokens.css; el bloque táctil es la §13 de esta hoja. Aquí la
 * consecuencia práctica: NO se añade un @media (max-width: …) nuevo sin
 * excepción anotada; la regla base es la de móvil y min-width ensancha.
 *
 * REGLA: cero colores literales. Todo color sale de un token de tokens.css; si
 * hace falta uno nuevo, se añade allí, no aquí. La métrica fina va en px: es
 * medida de pieza y es la MISMA en los dos temas (M-7).
 *
 * LO QUE ESTE CATÁLOGO NO TRAE (y de quién es):
 *   · El chasis de aplicación (lateral, cabecera, cuerpo: .bf-app/.bf-side/
 *     .bf-top/.bf-body de la maqueta) vive DENTRO del Shadow DOM del shell y se
 *     viste en su propio CSS → chore 90.061 (U-2). Vestirlo desde aquí no
 *     serviría de nada: esta hoja no cruza esa frontera (sí lo hacen los tokens).
 *   · Las piezas propias de las pantallas de actividad —línea de tiempo,
 *     reproductor, hitos, escenario forense, barras por hora y mapa de calor—
 *     llegan con sus chores, que se apoyan en este catálogo. El reproductor y
 *     el escenario forense están en §9 (chore 90.054); las barras por hora, el
 *     mapa de calor y el resto del panorama, en §11 (chore 90.059).
 */

/* ═══ 1 · Base ═══════════════════════════════════════════════════════════════ */

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


/* ── LAS BARRAS DE DESPLAZAMIENTO, LAS DE HUGIN (hugin base.css:44-49,476-504) ──────
   Para TODO: el documento, las tablas que ruedan, las listas flotantes. En Chromium
   el pulgar es una píldora de 6 px dentro de una pista de 14 (el borde transparente
   la adelgaza), al 30 % del texto apagado y al 58 % al apuntar, sin flechas y sobre
   pista transparente. OJO: un elemento con `scrollbar-width` o `scrollbar-color` hace
   que Chromium ignore estas reglas y vuelva a la barra estándar con flechas; por eso
   las estándar sólo van donde no existe el pseudoelemento (Firefox). */
@supports not selector(::-webkit-scrollbar) {
  * {
    scrollbar-width: thin;
    scrollbar-color: color-mix(in srgb, var(--bf-muted) 32%, transparent) transparent;
  }
}

::-webkit-scrollbar {
  width: 0.875rem;
  height: 0.875rem;
}

::-webkit-scrollbar-button {
  display: none;
}

::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--bf-muted) 30%, transparent);
  background-clip: padding-box;
  border: 4px solid transparent;
  border-radius: 999px;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--bf-muted) 58%, transparent);
}

::-webkit-scrollbar-track,
::-webkit-scrollbar-corner {
  background: transparent;
}

html {
  font-size: var(--bf-font-size);
  /* El marco del shell ya no se va con el rodillo (chore 90.264): un salto a ancla o
     un scrollIntoView dejarían el destino DEBAJO de la cabecera. Es su alto medido
     —56 px, la cabecera de 3,5 rem de la chore 90.266— más un respiro. Desde la chore
     90.267 ese alto es el MISMO en todos los cortes: la barra del menú del móvil se fue
     con el cajón, y arriba queda una sola franja. En px, como el marco: con la letra
     fluida, en rem se descolgaría de él. */
  scroll-padding-top: 64px;
  /* Sin rebote al final de la página (chore 90.756): con el panel táctil, Edge estira el documento al pasarse del
     final y arrastra lo pegado (`sticky`) —el lateral y su lista de iconos— hacia arriba durante el rebote. El
     marco no debe moverse: la página llega a su final y se queda quieta. */
  overscroll-behavior-y: none;
}

/* Y por abajo, en el teléfono, la barra inferior del shell flota sobre la página: un
   ancla al final quedaría bajo su cristal (chore 90.267). */
@media (max-width: 619.98px) {
  html {
    scroll-padding-bottom: calc(80px + env(safe-area-inset-bottom, 0px));
  }
}

/* EL PASO DE UNA PÁGINA A OTRA (chore 90.269, T12). Esta línea es del PRODUCTO —cada
   verticalizado que quiera lo mismo la declara en su hoja— y con ella el cambio de página
   deja de ser un parpadeo: la vista entera cruza en un fundido corto.
   Lo que NO se consigue es que la pastilla del «aquí estás» viaje de una página a otra, y
   está medido (Chrome 153): un view-transition-name dentro del Shadow DOM del shell computa
   pero su elemento no se captura, así que no nace el grupo con el que emparejarla. La
   frontera es el shadow root —el propio <bifrost-shell> con nombre sí crea grupo—, no el
   cristal. T12 ya preveía este desenlace y es el que hay.
   El apagado va aquí también, y por las dos vías, porque estos pseudo-elementos NO viven en
   el árbol de sombra del shell y el «transition: none» de allí no les llega. */
@view-transition {
  navigation: none;
}

/* SIN PARPADEO AL CARGAR, COMO EN HUGIN (2026-09-28). El fundido de página entera de arriba se
   retira: el marco se fundía consigo mismo en cada carga y, mientras el shell no estaba definido, el
   contenido se pintaba un instante SIN marco y luego saltaba dentro — el parpadeo. Ahora:
   1. lo que va dentro del shell no se pinta hasta que el shell existe, así que el navegador mantiene
      la página anterior hasta tener la nueva entera (y si el shell no llegara, aparece igual a los
      2,5 s: el contenido nunca se queda escondido);
   2. el marco sale ya puesto, quieto;
   3. la VISTA entra con el gesto de Hugin (`item-in`: sube 0,9 rem y crece un pelo, 520 ms con su
      curva), también cada vez que la capa de navegación trae otra página. */
@media (scripting: enabled) {
  bifrost-shell:not(:defined) {
    opacity: 0;
    animation: bf-shell-late 0.2s ease 2.5s forwards;
  }
}

@keyframes bf-shell-late {
  to { opacity: 1; }
}

@keyframes bf-view-in {
  from { opacity: 0; transform: translateY(0.9rem) scale(0.985); }
}

/* `backwards` y no `both` (chore 90.812): con `both` la vista se quedaba al acabar con su último fotograma fijado
   —una transformación identidad, pero animada— y eso la convertía en su propia capa: dentro de ella NINGÚN cristal
   desenfocaba lo de detrás (medido: cabeceras de tabla, banda de filtros, cualquier `backdrop-filter` de la vista).
   `backwards` mantiene el primer fotograma antes de arrancar y, al acabar, suelta la vista. */
bifrost-shell > [data-nav-region] {
  animation: bf-view-in 520ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
}

@media (prefers-reduced-motion: reduce) {
  bifrost-shell > [data-nav-region] {
    animation: none;
  }
}

:root[data-reduce~="motion"] bifrost-shell > [data-nav-region] {
  animation: none;
}

/* LA ENTRADA TRAS EL LOGIN (chore 90.653). La primera carga de la pestaña tras iniciar sesión coloca la aplicación:
   el shell mueve su chasis y aquí se asienta el contenido. Lo dice el shell en <html> con `data-bf-entry` —`hold`
   mientras espera al contexto, `play` al colocar— y ts/ui/entry.ts marca, al ver `play`, la región
   (`data-entry-region`) y los bloques que se ven (`data-entry-i`, con su escalón en --bf-entry-i). Las marcas se quedan
   en los nodos para que, al acabar, no vuelva la entrada propia de cada bloque. La región se funde y los bloques llegan
   en cascada con fundido, una subida corta, un pelo de escala y un desenfoque que se aclara: sólo opacidad, transformación
   y filtro, así que nada se mueve en el flujo, y todo está en el árbol y enfocable mientras dura. Con movimiento reducido,
   por cualquiera de las dos vías, no hay nada que apartar ni que animar (el shell tampoco la enciende). */
@media (prefers-reduced-motion: no-preference) {
  :root[data-bf-entry="hold"]:not([data-reduce~="motion"]) bifrost-shell > [data-nav-region] {
    opacity: 0;
    animation: none;
  }

  :root:not([data-reduce~="motion"]) bifrost-shell > [data-nav-region][data-entry-region] {
    animation: bf-entry-region var(--bf-dur-entry) var(--bf-ease-out) 120ms both;
  }

  :root:not([data-reduce~="motion"]) [data-nav-region] [data-entry-i] {
    animation: bf-entry-settle var(--bf-dur-entry) var(--bf-ease-out) both;
    animation-delay: calc(180ms + var(--bf-entry-i, 0) * var(--bf-dur-entry-cascade));
  }
}

@keyframes bf-entry-region {
  from { opacity: 0; }
}

@keyframes bf-entry-settle {
  from { opacity: 0; transform: translateY(0.9rem) scale(0.975); filter: blur(4px); }
}

/* CAMBIAR DE PESTAÑA NO ES CAMBIAR DE PÁGINA (chore 90.447): la capa marca la región con `data-nav-tab` y ésta no
   hace su entrada —la cabecera, el título y el carril de pestañas se quedan quietos; la pastilla viaja—. Lo de
   debajo de las pestañas entra con un fundido corto; la cabecera del registro, que es la misma, tampoco. */
bifrost-shell > [data-nav-region][data-nav-tab] {
  animation: none;
}

[data-nav-tab] .bf-tabs ~ :not(:has(> .bf-rechead)) {
  animation: bf-tab-in var(--bf-dur-base) var(--bf-ease-out) both;
}

@keyframes bf-tab-in {
  from { opacity: 0; transform: translateY(0.25rem); }
}

@media (prefers-reduced-motion: reduce) {
  [data-nav-tab] .bf-tabs ~ * { animation: none; }
}

:root[data-reduce~="motion"] [data-nav-tab] .bf-tabs ~ * {
  animation: none;
}

/* EL CAMBIO DE TEMA (chore 90.271, T14) usa la misma maquinaria, pero no es una navegación: la
   arranca el shell sobre el documento que ya está puesto, y dura lo que el sistema de diseño dice
   —320 ms— en vez de los 250 de serie del navegador. Se distinguen por el atributo que el shell
   pone mientras dura, que es lo único que separa un fundido del otro: sin él, fijar la duración
   aquí se la cambiaría también al paso de una página a otra, que la chore 90.269 dejó como estaba.
   El apagado no se repite: las reglas de arriba alcanzan a CUALQUIER ::view-transition, ésta
   incluida, por las dos vías. */
:root[data-theme-switch]::view-transition-old(root),
:root[data-theme-switch]::view-transition-new(root) {
  animation-duration: var(--bf-dur-theme, 320ms);
  animation-timing-function: var(--bf-ease-theme, cubic-bezier(0.22, 1, 0.36, 1));
}

/* EL CAMBIO DE PÁGINA SIN RECARGA (chore 90.316). La capa de navegación de la administración
   (ts/ui/navigate.ts) pone el contenido nuevo en el sitio del de antes, y lo hace dentro de una
   transición de vista del MISMO documento: es la misma maquinaria que el paso entre páginas de
   arriba, pero la arranca la capa, y se marca con su propio atributo mientras dura. El fundido es
   corto —lo que aparece en su sitio, M-13— porque lo que cambia es el contenido y el marco se queda
   quieto: un cruce largo haría esperar por algo que ya está. Seco con el movimiento reducido por las
   dos vías: las reglas de abajo alcanzan a cualquier ::view-transition, y además la capa ni la
   arranca.
   SIN ESPACIO entre `:root[…]` y el pseudo-elemento (chore 90.681): los `::view-transition-*` nacen del propio
   <html>, no de un descendiente suyo, y con el espacio la regla no casaba nunca. El cruce de serie del navegador
   —250 ms de la página vieja fundiéndose sobre la nueva, marco incluido— seguía puesto en cada paso de la capa, y
   al cambiar de pestaña en una ficha se veía la ficha entera desvanecerse como en una recarga. */
:root[data-nav-swap]::view-transition-old(root),
:root[data-nav-swap]::view-transition-new(root) {
  animation: none;
}

/* LA BARRA DE PROGRESO de esa misma capa (chore 90.316). Sale SÓLO si la petición tarda más de
   ~150 ms —lo que llega antes no necesita decir que se espera— y va arriba del todo, fina y por
   encima del marco, porque es la única pista de que algo está en camino: el marco no se desmonta y
   la página de antes sigue a la vista hasta que llega la siguiente.
   Dice «esperando», y eso en el sistema se dice con el barrido del esqueleto (§3): el mismo bucle,
   el mismo token (--bf-dur-sweep) y la misma parada con el movimiento reducido. No es un tercer
   bucle: es el de «esperando» en otra pieza. El color es la marca, que es «aquí pasa algo tuyo».
   Es decoración: quien dice que se espera es el `aria-busy` del contenido. Por encima de todo
   (110, sobre la pista) porque no recibe el puntero ni tapa nada que haga falta pulsar. */
.bf-navbar {
  position: fixed;
  z-index: 110;
  top: 0;
  right: 0;
  left: 0;
  height: 3px;
  pointer-events: none;
  background-color: color-mix(in srgb, var(--bf-accent) 24%, transparent);
  background-image: linear-gradient(90deg, transparent 20%, var(--bf-accent) 50%, transparent 80%);
  background-repeat: no-repeat;
  background-size: 240% 100%;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--bf-dur-base) var(--bf-ease-out),
    visibility 0s linear var(--bf-dur-base);
  animation: bf-skel-sweep var(--bf-dur-sweep) linear infinite;
  animation-play-state: paused;
}

.bf-navbar[data-on] {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--bf-dur-base) var(--bf-ease-out);
  animation-play-state: running;
}

@media (prefers-reduced-motion: reduce) {
  .bf-navbar {
    animation: none;
    background-image: none;
    background-color: var(--bf-accent);
  }
}

:root[data-reduce~="motion"] .bf-navbar {
  animation: none;
  background-image: none;
  background-color: var(--bf-accent);
}

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

:root[data-reduce~="motion"]::view-transition-group(*),
:root[data-reduce~="motion"]::view-transition-old(*),
:root[data-reduce~="motion"]::view-transition-new(*) {
  animation: none !important;
}

body {
  margin: 0;
  padding: 0;
  /* MF-6: dvh con respaldo — el 100vh móvil no descuenta la barra del navegador. */
  min-height: 100vh;
  min-height: 100dvh;
  font-family: var(--bf-font-family);
  font-size: var(--bf-body-font-size);
  line-height: 1.45;
  background-color: var(--bf-app-bg);
  /* El resplandor de las vistas de Hugin: un radial de su tono de lienzo arriba a la
     izquierda, fijo al hacer scroll. En claro el token es transparente. */
  /* Y desde la chore 90.762, las dos manchas de color del mockup del seleccionado (mismas proporciones: la cálida
     arriba a la derecha, la azul abajo a la izquierda), fijas como el resplandor: el cristal de encima se nota. */
  /* Desde la 90.765 la composición entera es un token del Host (--bf-view-art, tokens.css): se afina sin tocar
     el shell. Sin él, el resplandor de siempre. */
  background-image: var(--bf-view-art, radial-gradient(115% 85% at 0% 0%, var(--bf-view-glow, transparent) 0%, transparent 65%));
  background-attachment: fixed;
  color: var(--bf-surface-fg);
  /* Cifras TABULARES en todo el cuerpo (contrato de diseño §2.5 · chore 90.252).
     Esta aplicación enseña números que se leen en columna —contadores, sesiones,
     caducidades, medidas— y con cifras proporcionales un 1 mide menos que un 8: dos
     filas con el mismo número de dígitos salen de distinto ancho y la columna baila
     al refrescarse. Lo hacía .bf-tnum pieza a pieza; ahora es el punto de partida y
     esa clase queda para reforzarlo donde alguien lo haya desactivado. */
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
h4,
p {
  margin: 0;
  letter-spacing: 0;
}

h1,
h2,
h3,
h4 {
  font-weight: 600;
  color: var(--bf-surface-fg);
}

h1 {
  font-size: var(--bf-title-font-size);
  letter-spacing: -0.015em;
}

h2 {
  font-size: var(--bf-panel-title-font-size);
}

h3 {
  font-size: var(--bf-panel-title-font-size);
}

a {
  color: var(--bf-accent-text);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

/* Foco visible: el único anillo que sobrevive al «sin sombras» (M-4), junto con
   los halos de estado. La maqueta lo enseña estático (.bf-input.focus); aquí es
   real y global, que es lo que exige el teclado.

   ANILLO DOBLE (chore 90.147; la 90.136 lo estrenó en el botón y aquí baja al
   suelo de TODOS los controles). El naranja de marca NO llega solo sobre las
   superficies claras: 2.80:1 contra la tarjeta, 2.47:1 contra el fondo de la
   aplicación y 2.38:1 contra el relleno hundido, cuando SC 1.4.11 pide 3:1
   también al indicador de foco. En oscuro sí llega de sobra (5.61:1, 6.21:1 y
   5.82:1). Recolorearlo no es la salida: es el naranja único de M-1, congelado
   en la maqueta.

   La segunda capa es una línea de tinta pegada al control —--bf-surface-fg, que
   sigue al tema— y es la que cumple donde el naranja no: 14.27:1, 12.59:1 y
   12.10:1 en claro; 13.45:1, 14.88:1 y 13.95:1 en oscuro. Basta con que UNA de
   las dos capas contraste con lo que tenga detrás: de eso va la técnica.

   Sobre la PIZARRA —el lateral y el panel de acceso, oscuros en los DOS temas—
   el reparto se invierte: la tinta del tema claro se apaga (1.16:1) y quien
   cumple es el naranja (4.40:1 sobre el lateral claro, 6.39:1 sobre el oscuro).
   Sigue habiendo, siempre, una capa que se ve.

   M-4 lo admite: de la elevación por sombra sólo se salvan los anillos de foco y
   los halos de estado. OJO al añadir piezas: una que lleve halo de estado propio
   en `box-shadow` tiene que volver a declararlo en su regla de foco, o el anillo
   se lo lleva por delante. */
:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--bf-surface-fg);
}

/* `hidden` gana a la pieza (chore 90.052). El catálogo pinta casi todo con
   display:flex, y una clase le gana en cascada al display:none que el navegador
   trae para [hidden]: sin esta regla, un .bf-empty o un .bf-msg marcado como
   oculto se seguiría viendo. Va aquí, en la base, para que valga en TODA pieza
   que una pantalla esconda y muestre desde el front. */
[hidden] {
  display: none !important;
}

/* Cifras que se comparan en columna y claves técnicas. El marcado de la maqueta
   las escribe sin prefijo; se aceptan las dos formas. */
.bf-tnum,
.tnum {
  font-variant-numeric: tabular-nums;
}

.bf-mono,
.mono {
  font-family: var(--bf-font-mono);
  font-variant-numeric: tabular-nums;
}

/* ═══ 2 · Controles ══════════════════════════════════════════════════════════ */

/* ── El botón de acción ────────────────────────────────────────────────────────
   VESTIDO CON EL SISTEMA en la chore 90.254 (contrato de diseño §3). Lo que había
   era la escala de las chores 90.136 y 90.214 —34 px, relleno opaco propio y el
   primario naranja con texto blanco—, y cambian tres cosas:

   1 · EL ALTO, a --bf-control-height: 1,7rem, unos 27 px. Botón, campo y
       desplegable miden LO MISMO (S-11). Si el botón no baja con los campos, la
       fila que tiene los dos deja de estar alineada, y un renglón torcido se ve
       antes que un botón alto.

   2 · EL MATERIAL, de relleno opaco a VELO: el mismo del campo. En una fila con un
       buscador y un botón, los dos son la misma clase de cosa —algo que se pulsa o
       donde se escribe— y hasta ahora se pintaban con dos grises distintos. Y como
       es un velo, el realce no puede ser OTRO COLOR opaco: es otro velo ENCIMA,
       apilado con background-image, y el pulsado son dos. Por eso --bf-button-hover-bg
       y --bf-button-press-bg se quedan sin uso.

   3 · EL PRIMARIO DEJA DE SER EL NARANJA DE MARCA, y esto REESCRIBE M-1. El
       #ee7b24 con texto blanco mide 2,80:1 y suspende AA para texto (4,5:1). Estaba
       medido y anotado desde la 90.136, pero cambiarlo entonces era cambiar la
       maqueta congelada. La acción primaria pasa al CTA con tinta oscura: 6,73:1 en
       claro y 5,52:1 en oscuro. El naranja se queda donde sí cumple —«aquí estás»,
       acentos y filetes—, que es lo que M-1 quería decir de verdad.

   M-12 SIGUE: el filete que lleva el botón es el del control (--bf-field-line), ya
   calibrado a los 3:1 de SC 1.4.11. Y el foco no tiene regla propia desde la chore
   90.147: es el anillo del reset global. */
.bf-btn {
  height: var(--bf-btn-height); /* el alto único de botón (chore 90.719) */
  padding: var(--bf-button-padding);
  border-radius: var(--bf-button-radius);
  /* SIN FILETE (chore 90.381): el secundario es sólo velo, como el `.btn` de Hugin.
     El píxel se queda, transparente, para que el alto no cambie al lado de un
     primario (que pinta su canto con el CTA) ni al apuntarlo. */
  border: 1px solid transparent;
  background-color: var(--bf-button-bg);
  color: var(--bf-button-fg);
  font-size: var(--bf-button-font-size);
  font-family: inherit;
  font-weight: 600;
  /* El renglón A RAS. Sin esto la palabra trae la interlínea heredada —21 px de
     caja para 14 de letra— y al centrar contra el glifo se centran dos cajas de
     altos distintos: las letras se van un pelo arriba y el icono queda bajo. */
  line-height: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.375rem;
  white-space: nowrap;
  text-decoration: none;
  cursor: pointer;
  /* El color cambia en 200 ms y con la curva de la casa. Es el primer sitio del
     catálogo que gasta los tokens de movimiento de la chore 90.253. */
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
}

/* El glifo, DE LA MEDIDA DE SU PALABRA. A 16 px con el texto a 14 el icono pesa
   más que lo que el botón dice, y una fila de cuatro se lee como cuatro iconos con
   una etiqueta al lado. */
.bf-btn > svg {
  width: 0.875rem;
  height: 0.875rem;
  flex-shrink: 0;
}

.bf-btn:hover {
  text-decoration: none;
}

/* El realce, APILADO sobre el velo. Un color opaco aquí devolvería al botón el
   material que acaba de dejar: al apuntarlo dejaría de ser la misma cosa que el
   campo de al lado. Dos capas del mismo velo para el pulsado, que es exactamente
   «un poco más» sin inventar un tercer color. */
.bf-btn:hover:not(:disabled) {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-btn:active:not(:disabled) {
  background-image:
    linear-gradient(var(--bf-hover), var(--bf-hover)),
    linear-gradient(var(--bf-hover), var(--bf-hover));
}

/* Acción primaria: el CTA, no el naranja de marca (ver el punto 3 de arriba).
   SIN ceja de luz arriba: sobre un relleno macizo, una línea clara en el canto
   superior no se lee como luz sino como un borde a medias, y al lado de un botón
   secundario que no la lleva, canta. El relieve de un primario lo da su color, que
   ya es el más fuerte de la pantalla. */
.bf-btn.primary {
  background-color: var(--bf-cta);
  background-image: none;
  border-color: var(--bf-cta);
  color: var(--bf-on-cta);
}

/* El icono de la acción (chore 90.378, BifrostActionGlyphs): todo primario lo lleva
   delante, como el `.btn-primary` de Hugin. Medida y hueco los da `.bf-btn > svg`;
   aquí sólo el trazo, más grueso que el de un secundario porque sobre el relleno
   macizo un trazo fino se deshace (mismo 2,4 que Hugin). Tinta = currentColor, así
   que sigue a --bf-on-cta en claro y oscuro. */
.bf-btn.primary > svg {
  stroke-width: 2.4;
}

.bf-btn.primary:hover:not(:disabled) {
  background-color: var(--bf-cta-hover);
  background-image: none;
  border-color: var(--bf-cta-hover);
}

.bf-btn.primary:active:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-cta), var(--bf-shadow-ink) 12%);
  background-image: none;
  border-color: color-mix(in srgb, var(--bf-cta), var(--bf-shadow-ink) 12%);
}

/* EL HOVER SE MUEVE (chore 90.384): además del tono, el botón se levanta un punto,
   el primario toma una caída suave de su propio color y el icono se adelanta; al
   pulsar se hunde. Sólo transform y box-shadow, que no reflujan, con la curva de la
   casa. Con «menos movimiento» se queda sólo el cambio de tono. */
.bf-btn {
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out),
    transform var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

.bf-btn > svg {
  transition: transform 0.3s var(--bf-ease-out);
}

.bf-btn:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(-1px);
}

.bf-btn:hover:not(:disabled):not([aria-disabled="true"]) > svg {
  transform: translateX(1px) scale(1.08);
}

/* EL CTA, PLANO COMO EN HUGIN (chore 90.627): el `.btn-primary` de Hugin (gallery.css) al apuntarlo SÓLO cambia el
   tono del fondo —a --cta-hover-color, el mismo par que --bf-cta/--bf-cta-hover— y al pulsarlo se oscurece un 12 %:
   ni se levanta, ni echa la caída de su color, ni adelanta el icono, ni se encoge. Lo de arriba (90.384) sigue para
   los secundarios; el primario lo anula aquí, en toda la aplicación y en los dos temas. El aro del foco, igual. */
.bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]),
.bf-btn.primary:active:not(:disabled):not([aria-disabled="true"]) {
  transform: none;
  box-shadow: none;
}

.bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) > svg {
  transform: none;
}

.bf-btn:active:not(:disabled):not([aria-disabled="true"]) {
  transform: translateY(0) scale(0.97);
  box-shadow: none;
  transition-duration: 80ms;
}

@media (prefers-reduced-motion: reduce) {
  .bf-btn,
  .bf-btn > svg {
    transition:
      background-color var(--bf-dur-base) var(--bf-ease-out),
      border-color var(--bf-dur-base) var(--bf-ease-out),
      color var(--bf-dur-base) var(--bf-ease-out);
  }

  .bf-btn:hover:not(:disabled),
  .bf-btn:active:not(:disabled),
  .bf-btn:hover:not(:disabled) > svg {
    transform: none;
  }
}

/* El aro del foco es NARANJA y lo relleno también: sobre sí mismo no se vería. En
   todo lo que va lleno de color de marca el aro se pinta con la TINTA del botón y
   pegado al canto, porque el hueco del aro general dejaría naranja sobre naranja. */
.bf-btn.primary:focus-visible,
.bf-btn.danger.solid:focus-visible {
  outline-color: currentColor;
  outline-offset: -4px;
}

/* `quiet` no tiene caja a propósito: es la acción terciaria. Lo que se le da es
   RESPUESTA — al apuntarlo o enfocarlo se enciende el velo y el texto sube a tinta.
   No se queda en --bf-muted porque sobre ese velo valdría 3,88:1 y AA pide 4,5.
   ── CUÁNDO SE USA `quiet` (regla, chore 90.137) ─────────────────────────────
   El propietario señaló el «Cerrar» de la tabla de sesiones: «parece un texto
   plano». No lo parecía — lo ERA, y cerraba la sesión de otra persona. El barrido
   encontró siete más con el mismo desvío, así que la regla se escribe aquí, que es
   donde se lee antes de elegir variante:
     · `quiet` SÍ — lo TERCIARIO y lo que DESCARTA: limpiar o soltar un filtro,
       cerrar una capa (drawer, modal, ficha), cancelar, volver al listado, y los
       conmutadores de vista (tarjeta ⇄ tabla). Son cosas que deshacen o que
       devuelven al sitio de antes; con caja llenarían de marcos las barras de
       filtros, que es el error contrario y también está prohibido.
     · `quiet` NO — todo lo que CAMBIA DATOS (revocar, marcar, borrar, invitar) y
       todo lo que es la acción PRINCIPAL de su fila o de su capa. Lleva caja:
       `danger` si echa a alguien o destruye, la base en los demás casos.
   La duda se resuelve preguntando qué pasa al pulsar: si algo cambia o si te lleva
   al sitio al que ibas, lleva caja. Tres pantallas dibujaban «cerrar sesión» de
   tres formas distintas por no tener esto escrito. */
.bf-btn.quiet {
  background-color: transparent;
  border-color: transparent;
  color: var(--bf-muted);
}

.bf-btn.quiet:hover:not(:disabled),
.bf-btn.quiet:focus-visible {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
  color: var(--bf-surface-fg);
}

/* Destructivo: TINTADO, no rojo macizo. El rojo lleno pertenece al vocabulario de
   estado —«rojo = algo falló»— y un botón que espera una orden no es una alarma
   (S-6). Con canto propio, porque un relleno al 18 % sobre una superficie
   translúcida es un botón que hay que buscar, y éste es el que borra: el canto lo
   delimita sin subirle el tono al relleno. */
.bf-btn.danger {
  color: var(--bf-danger-text);
  border-color: transparent;
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-danger) 45%, transparent);
  background-color: color-mix(in srgb, var(--bf-danger) 18%, transparent);
}

.bf-btn.danger:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-danger) 28%, transparent);
  background-image: none;
}

.bf-btn.danger:active:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-danger) 34%, transparent);
  background-image: none;
}

/* Y AQUÍ SÍ, ROJO MACIZO: la confirmación destructiva del diálogo (S-6). Es el
   último paso, el mensaje ya nombra lo que se va a perder y tiene que pesar más que
   el «Cancelar» de al lado. Los dos tokens que usa están calibrados por tema
   —en claro el rojo baja un 6 % para que la letra blanca apruebe—; el porqué, en
   tokens.css. NO se usa fuera de una confirmación: un rojo lleno en una fila de
   tabla es una alarma que nadie ha disparado. */
.bf-btn.danger.solid {
  background-color: var(--bf-danger-solid);
  background-image: none;
  border-color: var(--bf-danger-solid);
  box-shadow: none;
  color: var(--bf-on-danger);
}

.bf-btn.danger.solid:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-danger-solid), var(--bf-relief-light) 12%);
  border-color: color-mix(in srgb, var(--bf-danger-solid), var(--bf-relief-light) 12%);
}

.bf-btn.danger.solid:active:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-danger-solid), var(--bf-shadow-ink) 10%);
  border-color: color-mix(in srgb, var(--bf-danger-solid), var(--bf-shadow-ink) 10%);
}

/* ── OCUPADO, que no existía ───────────────────────────────────────────────────
   Emitir un informe, guardar una configuración o revocar cien sesiones tarda, y
   hasta ahora el botón no decía nada mientras corría. Quien lo ANUNCIA es
   aria-busy —el aro es sólo el eco visual—, y se sustituye el ICONO, nunca la
   etiqueta: así se sigue leyendo qué se está haciendo y el botón no cambia de ancho
   a mitad de gesto.

   OCUPADO NO ES APAGADO (S-9). Quien pone aria-busy suele deshabilitar el botón a
   la vez —es lo que impide la segunda pulsación, que sería una segunda escritura—,
   y con eso se llevaba por delante la cara de apagado: tinta al 45 % justo en el
   momento en que hay que VER que la cosa sigue viva. Un botón apagado dice «no se
   puede», que es lo contrario de lo que está pasando. Quien dice «espera» es el
   aro, así que aquí la opacidad vuelve a 1. */
.bf-btn[aria-busy="true"] {
  pointer-events: none;
}

.bf-btn[aria-busy="true"]:disabled {
  opacity: 1;
  color: var(--bf-button-fg);
}

.bf-btn[aria-busy="true"] > svg {
  display: none;
}

.bf-btn[aria-busy="true"]::before {
  content: "";
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  animation: bf-btn-spin 0.6s linear infinite;
}

@keyframes bf-btn-spin {
  to {
    transform: rotate(1turn);
  }
}

/* La única animación del catálogo con duración escrita a mano, y por eso lleva su
   propio apagado: las duraciones de tokens.css valen un milisegundo cuando hay que
   reducir el movimiento, y un milisegundo por vuelta no para el aro, lo convierte en
   un ventilador. Quieto sigue informando —es un aro incompleto, no un círculo—, y
   quien lo anuncia de verdad es aria-busy, que no depende de que nada gire. */
@media (prefers-reduced-motion: reduce) {
  .bf-btn[aria-busy="true"]::before {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-btn[aria-busy="true"]::before {
  animation: none;
}

/* Los dos tamaños, en rem y RECOLOCADOS bajo el nuevo base. Al bajar el mando a
   1,7rem los 28 px del pequeño quedaban POR ENCIMA de él: no es que se quedara sin
   sitio por poco, es que el orden se invertía. Baja a 1,5rem (24 px), que deja tres
   píxeles largos de diferencia — con 1,6rem, que es lo que usa el sistema de origen,
   pequeño y normal quedan a un píxel, que es como no tener dos tamaños.
   El campo pequeño de las filas de tabla (.bf-input.sm, 30 px) tiene el mismo
   problema y lo resuelve la chore 90.255; si allí se elige otra relación, ésta la
   sigue. Bajo (pointer: coarse) los dos alcanzan los 44 px de MF-4, que es donde se
   mira si un objetivo es alcanzable con el dedo. */
/* Desde la chore 90.719 el pequeño y el grande miden LO MISMO que el normal (--bf-btn-height): el tamaño queda en
   la letra y el aire, no en el alto. */
.bf-btn.sm {
  height: var(--bf-btn-height);
  padding: 0 0.625rem;
  font-size: var(--bf-button-font-size-small);
}

.bf-btn.lg {
  height: var(--bf-btn-height);
  padding: 0 1.125rem;
  font-size: var(--bf-text-body);
}

.bf-btn.block {
  width: 100%;
  justify-content: center;
}

/* Apagado. Al recolocar la escala había que revisarlo (punto 5 de la chore): su
   relleno es `--bf-panel-bg`, que era justo el candidato natural para el botón
   normal — de haberlo usado, apagado y vivo habrían quedado indistinguibles.
   Se distinguen por el FILETE (1.28:1 y 1.29:1 contra los 3.46:1 y 3.54:1 del
   vivo) y por la tinta, no por el relleno: en claro los dos rellenos se parecen
   (1.07:1). En oscuro además hunde, porque allí el botón vivo levanta sobre la
   tarjeta y éste baja por debajo. */
.bf-btn:disabled {
  color: var(--bf-dim);
  background-color: var(--bf-panel-bg);
  background-image: none;
  border-color: var(--bf-border);
  box-shadow: none;
  cursor: not-allowed;
}

/* ── Campos ────────────────────────────────────────────────────────────────── */

.bf-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bf-field > label,
.bf-label {
  font-size: var(--bf-legend-font-size);
  line-height: 1rem;
  color: var(--bf-muted);
}

/* El campo es un VELO con filete (M-6 · chore 90.255). Era un pozo: un relleno
   calibrado por tema y el foco con halo. Ahora el relleno es un velo que se
   recompone sobre lo que tenga detrás —tarjeta, panel o diálogo— (--bf-field-bg),
   el filete es el de mando, a 3:1 (M-12), y el radio es el de CAMPO, no el del
   botón (--bf-field-radius). El foco y el error están más abajo, con su porqué.
   SIN CANTO desde la chore 90.399, como en Hugin (`.field-search`, `.select`,
   `.input-page`): --bf-field-edge vale transparente en los dos temas. Se aparta
   a sabiendas del filete a 3:1 de M-12: lo sigue Hugin, que lo quitó porque un
   campo es un hueco y el canto ahí se lee como un rectángulo pintado encima. El
   borde se APAGA y no se quita, para que foco y error no muevan la maqueta. */
.bf-input {
  height: var(--bf-field-height);
  border: 1px solid var(--bf-field-edge);
  border-radius: var(--bf-field-radius);
  background: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  font-size: var(--bf-field-font-size);
  font-family: inherit;
  display: flex;
  align-items: center;
  padding: var(--bf-field-padding);
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

/* Un control REAL no es el div de la maqueta: recupera su caja de bloque y el
   ancho de su contenedor. El resto de la receta (velo, filete, altura) es la
   misma, así que el marcado maquetado y el marcado real se ven igual. */
input.bf-input,
select.bf-input,
textarea.bf-input,
.bf-field input[type="text"],
.bf-field input[type="password"],
.bf-field input[type="email"],
.bf-field input[type="number"],
.bf-field input[type="tel"],
.bf-field input[type="url"],
.bf-field input[type="date"],
.bf-field input[type="search"],
.bf-field input[type="file"],
.bf-field select,
.bf-field textarea {
  display: block;
  width: 100%;
  height: var(--bf-field-height);
  margin: 0;
  padding: var(--bf-field-padding);
  border: 1px solid var(--bf-field-edge);
  border-radius: var(--bf-field-radius);
  background: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  font-family: inherit;
  font-size: var(--bf-field-font-size);
  outline: 0;
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

.bf-field textarea,
textarea.bf-input {
  height: auto;
  min-height: 5rem;
  padding: 8px 12px;
  resize: vertical;
}

/* Control COMPACTO, para el que vive dentro de una fila de tabla (chore 90.112: el
   desplegable de rol de la pestaña de asignaciones). Dos diferencias con el pozo de un
   formulario, y las dos las pide el sitio:
     · la métrica baja a la de la fila —30 px— para no engordar el renglón, igual que
       `.bf-btn.sm` hace con los botones de la columna de acciones; y
     · se CIÑE A SU CONTENIDO en vez de estirarse al ancho de su columna. Un desplegable
       de 380 px para elegir entre tres roles se lee como una caja vacía, no como un
       control. El tope del 100 % es lo que impide que un rol de nombre largo desborde. */
.bf-input.sm {
  /* DECIDIDO (chore 90.255): el pequeño se SEPARA, no se retira. Con el mando base
     en 1,7rem los 30 px de antes quedaban POR ENCIMA del normal —el orden de los dos
     tamaños se invertía— y había dos salidas:
       · retirarlo, y que el desplegable de rol de la fila de tabla midiera lo mismo
         que el de un formulario. Pero es la fila la que manda en su alto: un mando
         normal engorda el renglón, y ésa es la razón por la que el pequeño existe;
       · o bajarlo lo bastante para que se lea como otro tamaño. El sistema de origen
         lo lleva a 1,6rem, y a un píxel del normal NO se distinguen: dos tamaños que
         no se ven son uno con un error de redondeo.
     Así que 1,5rem (24 px), tres píxeles largos por debajo, y JUNTO con el botón
     pequeño (.bf-btn.sm): en una fila salen el uno al lado del otro, y si uno cambia
     el otro tiene que ir detrás. Desde la chore 90.719 el botón pequeño mide el alto único
     de botón, así que el campo pequeño va con él. */
  height: var(--bf-btn-height);
  font-size: var(--bf-button-font-size-small);
  width: auto;
  max-width: 100%;
}

/* El marcador, en la tinta apagada de Hugin (`.input::placeholder` → --muted-color;
   chore 90.399), la misma que el del buscador. */
.bf-input.ph {
  color: var(--bf-muted);
}

.bf-input::placeholder,
.bf-field input::placeholder,
.bf-field textarea::placeholder {
  color: var(--bf-muted);
}

/* EL HOVER TOCA LA SUPERFICIE Y NUNCA EL CANTO (S-5 · chore 90.255). Encender el
   canto al pasar el ratón por un formulario de ocho campos enciende uno y lo apaga
   al llegar al siguiente, y eso se lee como si algo hubiera cambiado de estado. Lo
   que dice «aquí se puede escribir» es la superficie: se le apila el velo de apuntar,
   el mismo que el botón. El canto se guarda para el foco y el error, que sí son
   estados. Ni lo congelado ni lo de sólo lectura responden: no se tocan.
   El desplegable queda fuera a propósito: su background-image es la flecha, y
   apilarle el velo se la llevaría por delante (tiene su propio realce, abajo). */
/* (Rediseño general) Aquí el campo apilaba el velo de apuntar al pasar el ratón.
   Hugin no responde al paso en sus campos —lo que dice «aquí se escribe» es el
   propio velo del campo y el cursor— y los campos de la familia tienen que verse
   iguales, así que se retira. El foco y el error siguen siendo los estados. */

/* ── Desplegable (chore 90.255) ─────────────────────────────────────────────
   Pieza nueva, no un retoque: el <select> se pintaba con la receta del campo y la
   flecha del navegador, que es el detalle que más delata un tema oscuro a medias.
   CONTROL OPACO (--bf-control-bg), no velo: Chromium pinta la lista desplegable con
   el fondo del control, y con un velo translúcido la lista salía transparente
   encima del contenido. Las opciones llevan el suyo escrito a mano porque la lista
   la dibuja el sistema y no hereda el tema. La flecha es PROPIA (appearance: none)
   y el hueco de la derecha se lo reserva el padding: 2,4rem dejan un dedo de aire
   entre lo que se lee y lo que se pulsa. De UNA línea siempre: sin la apariencia
   nativa, una opción larga en una columna estrecha partía el texto y la caja crecía
   a doble alto entre otras que no. */
select.bf-input,
.bf-field select {
  padding-right: 2.4rem;
  border-color: transparent;
  background-color: var(--bf-control-bg);
  background-image: var(--bf-select-arrow);
  background-repeat: no-repeat;
  background-position: right 0.6rem center;
  background-size: 0.75rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  appearance: none;
  cursor: pointer;
}

/* El realce del opaco: sube de plano, el canto no se mueve (S-5). */
select.bf-input:hover:not(:disabled):not(.frozen),
.bf-field select:hover:not(:disabled) {
  background-color: var(--bf-control-hover-bg);
}

select.bf-input > option,
.bf-field select > option {
  background-color: var(--bf-control-bg);
  color: var(--bf-surface-fg);
}

select.bf-input.sm {
  padding-right: 2.1rem;
  background-position: right 0.5rem center;
}

/* LA LISTA, DE CRISTAL, donde el navegador lo permita. `appearance: base-select`
   convierte la lista en un elemento de la página que se puede vestir entero; hasta
   aquí el resaltado de la fila apuntada era el azul del sistema. Va en un @supports
   y DESPUÉS de lo de arriba a propósito: donde no exista, la declaración no se
   entiende, se descarta, y se queda el control opaco con su flecha. Y en reglas
   aparte de todo lo demás: una lista de selectores con un pseudoelemento que el
   navegador no conoce se descarta ENTERA.
   Las dos preferencias que quitan cristal y movimiento llegan aquí solas: la lista
   hereda del <select> los tokens --bf-glass-* y --bf-dur-*, que son los que ellas
   cambian en la raíz (tokens.css). */
@supports (appearance: base-select) {
  select.bf-input,
  .bf-field select,
  select.bf-input::picker(select),
  .bf-field select::picker(select) {
    appearance: base-select;
  }

  /* Con la lista propia, el control deja el opaco y vuelve al VELO del campo: el
     opaco existía sólo porque la lista del sistema salía transparente. Y la flecha
     la pone el navegador (::picker-icon): la de fondo sobraría y saldrían dos. El
     realce vuelve a ser el del campo, apilado: ya no hay flecha que tapar. */
  select.bf-input,
  .bf-field select {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding-right: 0.5rem;
    background-color: var(--bf-field-bg);
    background-image: none;
  }

  select.bf-input:hover:not(:disabled):not(.frozen),
  .bf-field select:hover:not(:disabled) {
    background-color: var(--bf-select-hover-veil);
    background-image: none;
    /* La posición y la medida de la flecha siguen puestas desde la regla base: sin
       esto el velo se pintaba del tamaño de la flecha, un recuadro a la derecha. */
    background-position: 0 0;
    background-size: auto;
  }

  /* La flecha del navegador es un triángulo macizo; se apaga su tinta y se pone la
     nuestra de fondo, para que los dos caminos se vean igual. Abierto, se da la
     vuelta: dice que lo de debajo es de este control. */
  select.bf-input::picker-icon,
  .bf-field select::picker-icon {
    margin-left: auto;
    width: 0.75rem;
    height: 0.75rem;
    color: transparent;
    background-image: var(--bf-select-arrow);
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
    transition: transform var(--bf-dur-fast) var(--bf-ease-out);
  }

  select.bf-input:open::picker-icon,
  .bf-field select:open::picker-icon {
    transform: rotate(180deg);
  }

  /* La lista: cristal de la casa, sin filete —la separa de lo de debajo el
     desenfoque y el bisel, no una línea—, y nace DE su mando, un pelo más arriba
     y un pelo más pequeña. Sin desplazamiento lateral: va alineada con el canto
     del campo, y moverla de lado la despegaría de él. */
  select.bf-input::picker(select),
  .bf-field select::picker(select) {
    margin-top: 0.25rem;
    padding: 0.375rem;
    border: none;
    border-radius: var(--bf-glass-radius);
    background: var(--bf-glass-surface);
    backdrop-filter: var(--bf-glass-filter);
    -webkit-backdrop-filter: var(--bf-glass-filter);
    box-shadow: var(--bf-glass-drop), var(--bf-glass-bevel);
    overscroll-behavior: contain;
    opacity: 0;
    transform: translateY(-0.4rem) scale(0.96);
    transform-origin: top center;
    transition:
      opacity var(--bf-dur-base) var(--bf-ease-out),
      transform var(--bf-dur-base) var(--bf-ease-out),
      display var(--bf-dur-base) allow-discrete,
      overlay var(--bf-dur-base) allow-discrete;
  }

  select.bf-input:open::picker(select),
  .bf-field select:open::picker(select) {
    opacity: 1;
    transform: none;
  }

  @starting-style {
    select.bf-input:open::picker(select),
    .bf-field select:open::picker(select) {
      opacity: 0;
      transform: translateY(-0.25rem) scale(0.98);
    }
  }

  select.bf-input option,
  .bf-field select option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.625rem;
    border-radius: var(--bf-radius-input);
    background-color: transparent;
    color: var(--bf-surface-fg);
    font-size: var(--bf-text-ui);
  }

  select.bf-input option:hover,
  select.bf-input option:focus,
  .bf-field select option:hover,
  .bf-field select option:focus {
    background-color: var(--bf-hover);
  }

  /* La elegida, con UNA señal: la marca, a la derecha, en la tinta de acento. El
     hueco lo reservan todas las filas (el `order` va en la marca), o la marcada
     empezaría su rótulo en otra columna que las demás. */
  select.bf-input option::checkmark,
  .bf-field select option::checkmark {
    order: 1;
    margin-left: auto;
    color: var(--bf-accent);
  }
}

/* TODOS LOS DESPLEGABLES, EL DE HUGIN (chore 90.743). Lo de arriba sólo alcanzaba a
   `select.bf-input` y `.bf-field select`: los de las barras de filtro (`select.bf-filter`,
   la pastilla `.bf-filter select`, el cajón de filtros), los de las fichas sueltas y los
   de las páginas C# seguían abriendo la lista del SISTEMA —blanca, con el azul de
   Windows en la fila apuntada—, así que en una misma pantalla convivían dos listas.
   Ahora es UNA regla para cualquier <select> de una línea (los de `multiple`/`size` son
   listas, no desplegables): la lista de cristal de `.select::picker(select)` de Hugin,
   su flecha fina que se da la vuelta al abrir, sus filas con el velo de apuntar, la
   marca de la elegida en el acento y su barra fina. Cada pieza conserva su caja —el
   velo, el opaco o la pastilla desvestida—: sólo cambia la flecha, que pasa de fondo a
   `::picker-icon` (con las dos se verían dos), y el hueco que le guardaba el relleno,
   que ahora ocupa ella (--bf-select-pad-end). `html:root` delante: tiene que ganarle a
   `appearance: none` y al `background` corto de las reglas de cada pieza, que van
   después en la hoja. Donde no haya `base-select` (Firefox, Safari) nada de esto se
   entiende y se queda lo de antes. */
.bf-filter select,
.bf-filter > select {
  --bf-select-pad-end: 0;
}

.bf-filterbox-body :is(.bf-filter > select, .bf-filterrow > select.bf-filter) {
  --bf-select-pad-end: 0.5rem;
}

@supports (appearance: base-select) {
  html:root select:not([multiple]):not([size]),
  html:root select:not([multiple]):not([size])::picker(select) {
    appearance: base-select;
  }

  html:root select:not([multiple]):not([size]) {
    align-items: center;
    gap: 0.5rem;
    padding-inline-end: var(--bf-select-pad-end, 0.5rem);
    background-image: none;
  }

  /* Con `base-select` el control mide lo ELEGIDO, no la opción más larga: un filtro en
     «Todas» se quedaba en 5 rem y crecía al elegir. El suelo de la `.sort-box > .select`
     de Hugin (8 rem) para los de las barras de filtro, que no saltan al cambiarlos. */
  html:root :is(select.bf-filter, .bf-filter > select):not([multiple]):not([size]) {
    min-inline-size: 8rem;
  }

  html:root select:not([multiple]):not([size])::picker-icon {
    margin-left: auto;
    width: 0.75rem;
    height: 0.75rem;
    color: transparent;
    background-image: var(--bf-select-arrow);
    background-repeat: no-repeat;
    background-position: center;
    background-size: contain;
    transition: transform var(--bf-dur-fast) var(--bf-ease-out);
  }

  html:root select:not([multiple]):not([size]):open::picker-icon {
    transform: rotate(180deg);
  }

  html:root select:not([multiple]):not([size])::picker(select) {
    margin-top: 0.25rem;
    padding: 0.375rem;
    border: none;
    border-radius: var(--bf-glass-radius);
    background: var(--bf-glass-surface);
    backdrop-filter: var(--bf-glass-filter);
    -webkit-backdrop-filter: var(--bf-glass-filter);
    box-shadow: var(--bf-glass-drop), var(--bf-glass-bevel);
    overscroll-behavior: contain;
    opacity: 0;
    transform: translateY(-0.4rem) scale(0.96);
    transform-origin: top center;
    transition:
      opacity var(--bf-dur-base) var(--bf-ease-out),
      transform var(--bf-dur-base) var(--bf-ease-out),
      display var(--bf-dur-base) allow-discrete,
      overlay var(--bf-dur-base) allow-discrete;
  }

  html:root select:not([multiple]):not([size]):open::picker(select) {
    opacity: 1;
    transform: none;
  }

  @starting-style {
    html:root select:not([multiple]):not([size]):open::picker(select) {
      opacity: 0;
      transform: translateY(-0.25rem) scale(0.98);
    }
  }

  /* Movimiento reducido: los tokens de duración ya bajan en la raíz, pero el sistema
     se pregunta aquí también porque una lista abierta es lo primero que se ve moverse. */
  @media (prefers-reduced-motion: reduce) {
    html:root select:not([multiple]):not([size])::picker(select),
    html:root select:not([multiple]):not([size])::picker-icon {
      transition-duration: 0.001ms;
    }
  }

  /* Las filas: las del menú de Hugin. Sin heredar el peso de la pastilla desvestida
     (`.bf-filter select` va en 600: la opción es una lista, no el valor puesto). */
  html:root select:not([multiple]):not([size]) option {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.375rem 0.625rem;
    border-radius: var(--bf-radius-input);
    background-color: transparent;
    color: var(--bf-surface-fg);
    font-size: var(--bf-text-ui);
    font-weight: 400;
  }

  html:root select:not([multiple]):not([size]) option:hover,
  html:root select:not([multiple]):not([size]) option:focus {
    background-color: var(--bf-hover);
  }

  html:root select:not([multiple]):not([size]) option::checkmark {
    order: 1;
    margin-left: auto;
    color: var(--bf-accent);
  }

  /* La barra de la lista, la fina de la casa: la lista es un pseudoelemento y la
     barra global (`::-webkit-scrollbar`) no la alcanza. */
  html:root select:not([multiple]):not([size])::picker(select)::-webkit-scrollbar {
    width: 0.875rem;
    height: 0.875rem;
  }

  html:root select:not([multiple]):not([size])::picker(select)::-webkit-scrollbar-button {
    display: none;
  }

  html:root select:not([multiple]):not([size])::picker(select)::-webkit-scrollbar-thumb {
    background-color: color-mix(in srgb, var(--bf-muted) 30%, transparent);
    background-clip: padding-box;
    border: 4px solid transparent;
    border-radius: 999px;
  }

  html:root select:not([multiple]):not([size])::picker(select)::-webkit-scrollbar-track,
  html:root select:not([multiple]):not([size])::picker(select)::-webkit-scrollbar-corner {
    background: transparent;
  }
}

/* EL FOCO ES EL CANTO (M-6 · chore 90.255), y sin aro. El canto se tiñe del CTA
   calibrado para canto (--bf-cta-edge, que llega a 3:1 donde el CTA a secas no) y
   ENGORDA de uno a dos píxeles. Dos señales a la vez, a propósito: el color dice
   «aquí» a quien lo distingue y el grosor se lo dice a quien no — el foco se ve sin
   depender del color. Sin aro alrededor porque el campo ya enseña su indicador
   propio, el cursor que parpadea dentro, y un anillo en torno a un campo de 27 px
   pesa más que toda la fila. El halo de antes (al 20 %) medía 1,21:1 en claro: nunca
   indicó nada.
   El campo apaga el `outline` global en su regla base; aquí se apaga también el
   `box-shadow` de tinta del foco global, que en un campo sería el aro que se quita.
   La maqueta enseña el estado como clase estática (.focus). */
.bf-input.focus,
.bf-input:focus,
.bf-field input:focus,
.bf-field select:focus,
.bf-field textarea:focus {
  outline: none;
  border-color: var(--bf-field-focus);
  box-shadow: inset 0 0 0 1px var(--bf-field-focus);
}

/* ── Validación (chore 90.255) ──────────────────────────────────────────────
   Un campo que no vale se marca EN EL CAMPO —canto rojo— y se explica DEBAJO, con
   la frase de .bf-hint.bad. F-8 sigue mandando en DÓNDE va cada error; esto cambia
   cómo se ve. El rojo es la regla: «rojo = algo falló».
   Y el campo da UN pulso al aparecer: el aro crece de golpe y se asienta en su
   tamaño, una sola vez. No es un temblor —un campo que tirita es justo lo que no
   soporta quien es sensible al movimiento—, sino lo que hace que el ojo vuelva al
   campo cuando la frase ya ha salido debajo y se estaba mirando otra cosa. Con
   movimiento reducido las duraciones valen 1 ms (tokens.css) y queda el canto
   quieto, que es la información.
   Sobre un control real hay que ganarle al selector por tipo, que trae el `border`
   en forma corta y se llevaría por delante el rojo; por eso se repite con el tipo. */
@keyframes bf-field-invalid-in {
  0% {
    box-shadow: 0 0 0 0 color-mix(in srgb, var(--bf-danger) 55%, transparent);
  }
  55% {
    box-shadow: 0 0 0 5px color-mix(in srgb, var(--bf-danger) 30%, transparent);
  }
  100% {
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-danger) 22%, transparent);
  }
}

.bf-input.err,
.bf-field input.err,
.bf-field select.err,
.bf-field textarea.err,
.bf-field input.bf-input.err,
.bf-input[aria-invalid="true"],
.bf-field :is(input, select, textarea)[aria-invalid="true"] {
  border-color: var(--bf-danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-danger) 22%, transparent);
  animation: bf-field-invalid-in var(--bf-dur-slow) var(--bf-ease-out);
}

/* Con el foco dentro, el canto SIGUE rojo —el error no se esconde por escribir en
   el campo— y engorda igual que el de foco: la señal de que se está aquí no se
   pierde por estar en error. Mismos selectores con :focus, escritos después, para
   ganarle a la regla de foco a igualdad de peso. */
.bf-input.err:focus,
.bf-field input.err:focus,
.bf-field select.err:focus,
.bf-field textarea.err:focus,
.bf-input[aria-invalid="true"]:focus,
.bf-field :is(input, select, textarea)[aria-invalid="true"]:focus {
  border-color: var(--bf-danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-danger) 22%, transparent);
}

/* Apagado como en Hugin (`.select:disabled`, `.input-page:disabled`; chore 90.399):
   el campo entero se DESVANECE (--bf-disabled-ink) y conserva su velo y, el
   desplegable, su flecha, que es lo que dice que ahí se elige (aunque ahora no).
   Antes cambiaba al relleno hundido, que sin canto se confundía con otra pieza. */
.bf-input:disabled,
.bf-field input:disabled,
.bf-field select:disabled,
.bf-field textarea:disabled {
  opacity: var(--bf-disabled-ink);
  cursor: not-allowed;
}

/* Campo CONGELADO (F-9 · chore 90.117). Lo INMUTABLE POR CONTRATO se ve y no se
   toca: la clave de un producto, la de un rol, la de un IdP, y el producto y la
   versión de una instancia. No es un campo deshabilitado —deshabilitado dice
   «ahora no», y esto dice «esto no cambia nunca»—, así que no se hunde en el
   pozo: fondo transparente, filete discontinuo y tinta apagada, con el
   `.bf-hint` de debajo contando POR QUÉ. Formaliza el trazo que «Mi perfil» ya
   dibujaba a mano (90.070).

   El selector se repite con el tipo delante por lo mismo que `.lg`: los
   selectores por `input[type=…]` de la base son MÁS específicos y le ganarían el
   fondo a `.bf-input.frozen` sobre un control real. */
.bf-input.frozen,
input.bf-input.frozen,
.bf-field input.bf-input.frozen,
.bf-field select.bf-input.frozen {
  background: transparent;
  border-style: dashed;
  /* El campo va sin canto (90.399), pero el congelado NO: su filete discontinuo es
     lo único que dice «esto no se toca», así que se enciende aquí a propósito. */
  border-color: var(--bf-field-line);
  color: var(--bf-muted);
}

/* CON EL FOCO SIGUE CONGELADO (chore 90.261). Con el campo de velo ya no hay pozo
   del que apartarse: en claro el velo del campo y el fondo transparente de éste
   quedan a un tono, y lo ÚNICO que dice «esto no se toca» es el filete discontinuo.
   El foco del campo editable (el canto del CTA ENGORDADO por dentro, 90.255) pinta
   una línea continua dentro de ese discontinuo y, mientras dura, el campo se leía
   como uno que se puede escribir. Aquí el canto no se mueve y el foco —que tiene que
   verse: se llega con el tabulador para leerlo o copiarlo— es el anillo global de
   cualquier objeto que no se edita. */
.bf-input.frozen:focus,
input.bf-input.frozen:focus,
.bf-field input.bf-input.frozen:focus,
.bf-field select.bf-input.frozen:focus {
  border-color: var(--bf-field-line);
  box-shadow: none;
}

.bf-input.frozen:focus-visible,
input.bf-input.frozen:focus-visible,
.bf-field input.bf-input.frozen:focus-visible,
.bf-field select.bf-input.frozen:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--bf-surface-fg);
}

.bf-input.lg {
  height: 44px;
  font-size: 15px;
  padding: 0 14px;
}

/* La talla grande sobre un control REAL. Hace falta repetirla con el tipo delante
   porque los selectores por `input[type=…]` de arriba son MÁS específicos que
   `.bf-input.lg`: sin esto, un campo de contraseña de 44 px se quedaba en 36 y la
   pantalla salía con dos alturas distintas. */
input.bf-input.lg,
.bf-field input.bf-input.lg,
.bf-field select.bf-input.lg {
  height: 44px;
  font-size: 15px;
  padding: 0 14px;
}

/* El desplegable grande conserva el hueco de su flecha (chore 90.255). */
select.bf-input.lg,
.bf-field select.bf-input.lg {
  padding-right: 2.6rem;
}

.bf-input .caret {
  width: 1px;
  height: 17px;
  background: var(--bf-accent);
  margin-left: 1px;
}

.bf-input .sel {
  margin-left: auto;
  color: var(--bf-dim);
  font-size: 11px;
}

/* La ayuda bajo el campo, como `.field-hint` de Hugin: 11 px y el apagado al 78 %. */
.bf-hint {
  font-size: 0.6875rem;
  line-height: 1.45;
  color: color-mix(in srgb, var(--bf-muted) 78%, transparent);
}

/* La frase del error ENTRA, no aparece (chore 90.255): un renglón que se
   materializa de golpe debajo de un campo es un salto de maquetación, y el ojo lo
   registra como «la página se ha movido» y no como «esto está mal». Cayendo desde
   el propio campo el movimiento SEÑALA de dónde viene el problema. Corto: es un
   aviso que se lee, no una entrada de escena. */
@keyframes bf-field-error-in {
  from {
    opacity: 0;
    transform: translateY(-0.25rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* El error, como `.field-error` de Hugin: 12 px, con su icono delante (lo escribe la
   página, si quiere) y en el rojo de texto. */
.bf-hint.bad {
  display: flex;
  gap: 0.375rem;
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--bf-danger-text);
  animation: bf-field-error-in var(--bf-dur-base) var(--bf-ease-out);
}

.bf-hint.bad > svg {
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
  margin-top: 0.125rem;
}

/* Dos notas seguidas son UN párrafo, no dos bloques: entre ellas va el hueco de
   una línea. Sin esto, dentro de una caja con ritmo (.in.stack) se separan lo
   mismo que dos piezas distintas y el pie de la pantalla se lee a trompicones. */
.bf-hint + .bf-hint {
  margin-top: 3px;
}

/* Lista de errores de UN campo (chore 90.556): cuando el servidor da varios motivos
   para el mismo control —el validador del manifiesto da uno por clave JSON—, van
   todos, uno por renglón y con la frase de .bf-hint.bad, bajo un recuento. Con
   muchos, la lista se desplaza dentro de su caja y el formulario no se va lejos. */
.bf-errlist {
  display: grid;
  gap: 4px;
}

.bf-errlist:focus {
  outline: none;
}

.bf-errlist > ul {
  display: grid;
  gap: 3px;
  max-height: 16rem;
  margin: 0;
  padding: 0 0 0 1.1rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.bf-errlist > ul > li.bf-hint.bad {
  display: list-item;
  overflow-wrap: anywhere;
}

/* ── Buscador de personas (chore 90.117) ───────────────────────────────────────
   La pieza `.bf-pick`: un campo de búsqueda cuyos resultados tienen CARA —avatar,
   nombre y usuario, la misma `.bf-who` de la tabla— porque elegir a una persona
   por su cuenta de usuario a secas es elegir a ciegas. Es UNA pieza para los dos
   sitios que la piden, el alta de asignación (90.073) y el aviso a una persona
   (90.114): dos buscadores distintos para lo mismo es justo lo que este catálogo
   existe para evitar.

   El panel sólo ofrece personas ACTIVAS —conceder a una de baja lo rechaza el
   servidor con un 409— y su pie dice cuántas se enseñan de cuántas hay, que es
   lo que avisa de que la lista está recortada. La maqueta lo dibuja extendido
   para poder leerlo; en el producto FLOTA sobre el formulario, y eso lo da el
   modificador `.float` — sin él cada pantalla se inventaría su posicionamiento.

   RE-VESTIDA en la chore 90.261 (contrato de diseño §3, «piezas que solo tiene
   Bifrost»). La forma no cambia: campo arriba, lista con cara debajo y pie con el
   recuento. Cambia el material. En las dos pantallas que la usan la lista NO flota:
   va en el flujo, debajo de su buscador, y es la otra mitad del mismo control. Por
   eso se viste de CAMPO —el velo del campo, el filete de mando a 3:1 (M-12) y el
   radio de campo— y no de tarjeta. La variante `.float` sí flota, y lo que flota es
   cristal (S-4): está en la lista de la receta, más abajo.
   Y se distinguen ahora TRES estados que antes eran uno: apuntada, elegida y con
   el foco se pintaban con el mismo relleno naranja, que es el fallo que la 90.256
   corrigió en el segmentado. El apuntado toca la superficie (S-5), la elegida lleva
   el tinte del acento con su carril del «aquí estás» (M-1) y el foco,
   su línea de tinta. */
.bf-pick {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.3125rem;
}

.bf-pick .pop {
  background-color: var(--bf-field-bg);
  border: 1px solid var(--bf-field-line);
  border-radius: var(--bf-field-radius);
  overflow: hidden;
}

/* Flotante: sale del flujo para no empujar el formulario al escribir. Sobre el
   campo, no debajo del todo, y a su mismo ancho. El material lo pone la receta
   del cristal (su selector está en la lista): sin filete, porque el canto de lo
   que flota es el bisel. Entra como los desplegables del sistema (.bf-more). */
.bf-pick.float .pop {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  z-index: 5;
  margin-top: 0.3125rem;
  border: 0;
  transform-origin: top center;
  animation: bf-pop-in var(--bf-dur-base) var(--bf-ease-out);
}

.bf-pick .opt {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.4375rem 0.6875rem;
  border-bottom: 1px solid var(--bf-border);
  width: 100%;
  text-align: left;
  background: none;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

/* El radio de cada opción, el del navegador con el acento, como el de `.bf-check`:
   sin esto salía el azul del sistema, que no es de ninguna pieza de la casa. */
.bf-pick .opt input[type="radio"] {
  flex: none;
  margin: 0;
  accent-color: var(--bf-accent);
}

/* Apuntada: sólo la superficie (S-5), el mismo velo que el realce de un mando. */
.bf-pick .opt:hover {
  background-color: var(--bf-hover);
}

/* ELEGIDA: el tinte del acento y el carril de acento a la izquierda, que es la
   marca de «aquí estás» (M-1) y no sombra de elevación (M-4). No el velo de
   seleccionado del segmentado: ése se lee sobre un carril gris, y sobre el velo
   blanco del campo, en claro, era blanco sobre blanco. `:has(:checked)` cubre el
   caso servido (chore 90.073): cuando la opción es un `<label>` con su radio
   dentro —que es lo que hace que el buscador funcione sin JavaScript—, la elegida
   la marca el navegador, no una clase del servidor. Sin hover propio: lo puesto no
   cambia al apuntarlo. */
.bf-pick .opt.on,
.bf-pick .opt:has(input:checked) {
  background-color: color-mix(in srgb, var(--bf-accent) 12%, transparent);
  box-shadow: inset 2px 0 0 var(--bf-accent);
}

/* EL FOCO, con su línea de tinta por dentro —por dentro, porque el panel recorta—:
   12,63:1 en claro y 13,58:1 en oscuro (anillo doble, 90.147). Se SUMA al estado
   que tenga la opción, no lo sustituye: la elegida con el foco sigue enseñando su
   carril, y la que no lo es toma el velo de apuntar para que se vea dónde está. */
.bf-pick .opt:focus-visible,
.bf-pick .opt:focus-within {
  outline: none;
  background-color: var(--bf-hover);
  box-shadow: inset 0 0 0 1px var(--bf-surface-fg);
}

.bf-pick .opt:is(.on, :has(input:checked)):is(:focus-visible, :focus-within) {
  background-color: color-mix(in srgb, var(--bf-accent) 12%, transparent);
  box-shadow:
    inset 2px 0 0 var(--bf-accent),
    inset 0 0 0 1px var(--bf-surface-fg);
}

/* El panel no crece sin fin: con doscientas personas dentro, la página entera se
   convertía en la lista. Se desplaza ÉL (MF-5), no la pantalla. Barra fina y con
   la tinta del tema, como la de la tabla (90.259). */
.bf-pick .pop {
  max-height: 21.25rem;
  overflow-y: auto;
}

/* Y su pie se queda a la vista mientras se recorre: es quien dice cuántas se
   están enseñando de cuántas hay, o sea, el aviso de que la lista está
   RECORTADA — al final del desplazamiento no lo lee nadie. */
.bf-pick .pfoot {
  position: sticky;
  bottom: 0;
}

.bf-pick .pfoot {
  padding: 0.375rem 0.6875rem;
  font-size: var(--bf-text-mini);
  color: var(--bf-dim);
  /* El hundido dejó de ser un relleno opaco y pasó a ser un VELO (chore 90.250),
     y este pie es `sticky`: con el velo solo se le verían las filas por debajo
     mientras la lista rueda. Va en dos capas —el velo encima de la superficie—
     para conservar el aspecto hundido SIN perder la opacidad. */
  background-color: var(--bf-surface-bg);
  background-image: linear-gradient(var(--bf-panel-bg), var(--bf-panel-bg));
  border-top: 1px solid var(--bf-border);
}

/* Dentro del cristal el pie es cristal también: una banda maciza partiría la lámina
   en dos. Lleva su propio desenfoque para que las filas que pasan por debajo se
   lean como la misma lista, como la cabecera pegajosa de la tabla (90.259); sin
   cristal, los dos tokens pasan solos a opaco (tokens.css). */
.bf-pick.float .pfoot {
  background-color: var(--bf-glass-surface);
  background-image: none;
  backdrop-filter: var(--bf-glass-filter);
  -webkit-backdrop-filter: var(--bf-glass-filter);
}

/* ── Pozo de pegado (chore 90.117) ─────────────────────────────────────────────
   Para credenciales largas: hoy, la licencia de una instancia (90.076), que es un
   JWT de varias líneas. Se pega el texto o se suelta el fichero —que se lee A
   TEXTO: lo que viaja es siempre el texto, nunca bytes—. Vacío enseña la
   invitación (`.ph`); lleno, el JWT en monoespaciada y recortado (`.jwt`), con el
   filete continuo, que dice que ya hay algo dentro.

   El `textarea` de dentro es donde se pega de verdad: va sin caja propia para que
   la caja sea el pozo y no haya dos bordes, uno dentro de otro.

   RE-VESTIDO en la chore 90.261: es un CAMPO, y se viste como el de la 90.255
   —velo, filete de mando, radio de campo, el apuntado en la superficie (S-5) y el
   foco en el canto, sin halo (M-6)—. Lo único suyo es el filete DISCONTINUO en
   vacío, que invita a soltar algo. No se confunde con el discontinuo de lo
   congelado (F-9): éste es más grueso, tiene relleno y trae la invitación escrita. */
.bf-paste {
  border: 1.5px dashed var(--bf-field-line);
  border-radius: var(--bf-field-radius);
  background-color: var(--bf-field-bg);
  padding: 0.8125rem 0.9375rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

.bf-paste:hover:not(:focus-within):not(.over) {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-paste .ph {
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

/* El recorte se cuenta en RENGLONES (4 × 1,5 = 6em), como el de la credencial
   (90.185): en píxeles, al crecer la letra con la raíz el cuarto renglón se partía. */
.bf-paste .jwt,
.bf-paste textarea {
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
  line-height: 1.5;
  overflow-wrap: anywhere;
}

.bf-paste .jwt {
  max-height: 6em;
  overflow: hidden;
}

.bf-paste textarea {
  border: 0;
  background: transparent;
  padding: 0;
  min-height: 6em;
  resize: vertical;
  width: 100%;
}

/* El `textarea` de dentro no dibuja NADA al enfocarse: quien enseña el foco es el
   pozo entero, con su `:focus-within`. Apaga también la línea de tinta que el
   reset le pondría (90.147), o volverían los dos bordes, uno dentro de otro. */
.bf-paste textarea:focus {
  outline: none;
  box-shadow: none;
}

/* TRES ESTADOS que se pintaban igual —el borde naranja con halo— y no dicen lo
   mismo (chore 90.261):
     · LLENO: el filete pasa a continuo y se queda en el color de mando. Ya hay
       algo dentro; no es una llamada de atención.
     · CON EL FOCO: el canto del CTA calibrado y ENGORDADO, sin aro, como un campo
       (M-6 · 90.255). El halo de antes medía 1,21:1 en claro: no indicaba nada.
     · `over`, con un fichero ENCIMA (chore 90.076): el acento y el mismo engorde.
       Es el «aquí va a caer» del destino de un arrastre —el mismo que pinta el
       árbol en modo reordenar—, porque soltar y pegar acaban en el mismo sitio: el
       texto del campo. */
.bf-paste.filled,
.bf-paste.over,
.bf-paste:focus-within {
  border-style: solid;
}

.bf-paste:focus-within {
  border-color: var(--bf-cta-edge);
  box-shadow: inset 0 0 0 1px var(--bf-cta-edge);
}

.bf-paste.over {
  border-color: var(--bf-accent);
  box-shadow: inset 0 0 0 1px var(--bf-accent);
}

/* ── Credencial (chore 90.184) ─────────────────────────────────────────────────
   La pieza `.bf-cred` es el ESPEJO del pozo de pegado: allí se suelta un secreto,
   aquí se recoge. Hoy la usa el texto de la licencia emitida (90.179) —lo que hay
   que llevarse a la pantalla de alta del producto—, y sirve para cualquier otra
   cosa que Bifrost entregue para copiar.

   Nace de que ese texto se pintaba con un `textarea.bf-input` a todo el ancho y
   el botón suelto debajo: se leía como un campo de formulario a medio rellenar y
   no como la credencial que es. Lo que cambia:
     · La etiqueta y el botón de copiar van en la MISMA línea, arriba: quien viene
       a copiar encuentra la acción donde mira, y no queda un hueco muerto entre
       el campo y la nota que lo explica.
     · Filete CONTINUO (el pozo de pegado lo lleva discontinuo): el discontinuo
       insinúa una zona donde soltar algo, y aquí no hay nada que soltar.
     · El campo sigue siendo un `textarea` de SÓLO LECTURA —decisión de 90.179: se
       selecciona entero con el teclado aunque el botón, que necesita JavaScript y
       portapapeles, no esté disponible—, pero sin caja propia ni tirador de
       redimensión: la caja es la pieza, y arrastrar la esquina de algo que no se
       edita no lleva a ninguna parte.
     · Alto ACOTADO y el resto se desplaza. Un JWT ocupa cuatro líneas hoy y ocho
       el día que la licencia declare topes: la pieza no puede crecer con el dato
       hasta empujar fuera de la pantalla lo que va debajo.
   RE-VESTIDA en la chore 90.261 con el material del campo (90.255): velo, filete de
   mando y radio de campo, y el foco en el canto y sin halo (más abajo). */
.bf-cred {
  border: 1px solid var(--bf-field-line);
  border-radius: var(--bf-field-radius);
  background-color: var(--bf-field-bg);
  padding: 0.625rem 0.75rem 0.6875rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

.bf-cred > .head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.bf-cred > .head .bf-label {
  margin-right: auto;
}

/* El alto se cuenta en LÍNEAS y no en píxeles (chore 90.185): `1.6em` es exactamente el
   `line-height` de aquí, así que el recorte cae siempre entre dos renglones. Con una
   medida redonda —92px— el quinto renglón se partía por la mitad y la pieza parecía
   rota, que es justo lo que un texto que hay que copiar entero no se puede permitir
   insinuar. */
.bf-cred .jwt,
.bf-cred textarea {
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-mini);
  line-height: 1.6;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
  max-height: calc(var(--bf-cred-lines, 5) * 1.6em);
  overflow-y: auto;
}

/* Talla LARGA, para lo que tiene forma propia y se lee de una pieza: un PEM son sus dos
   cercos y siete renglones, y enseñarlo entero es lo que deja comprobar de un vistazo que
   está completo. El JWT no la necesita: es una tira sin forma, y verla entera no dice nada
   que no diga verla a medias. */
.bf-cred.lg {
  --bf-cred-lines: 9;
}

/* El selector se repite con `.bf-input` detrás por lo mismo que en `.frozen`: los
   selectores por tipo de la base traen borde, fondo y alto en forma corta y le
   ganarían a la clase sobre un control real. */
.bf-cred textarea,
.bf-cred textarea.bf-input {
  display: block;
  width: 100%;
  height: auto;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  resize: none;
  outline: 0;
}

/* Igual que en el pozo de pegado: el campo de dentro no dibuja NADA al enfocarse
   —lo enseña la pieza entera— o habría dos bordes, uno dentro de otro. */
.bf-cred textarea:focus {
  box-shadow: none;
  border: 0;
}

/* El foco, en GRIS SECUNDARIO y no en el naranja de acción. Copiar hace
   `select()` sobre el campo, y eso lo enfoca: encender el color de «aquí se
   actúa» sobre un texto que sólo se lee dice lo que no es (M-3). No se resuelve
   quitando el indicador —el foco tiene que verse—, se resuelve cambiándolo de
   color: `--bf-muted` da 4.9:1 contra el pozo en claro y 5.1:1 en oscuro, de
   sobra para el 3:1 que pide 1.4.11, y se distingue del filete en reposo.
   Desde la chore 90.261 es el foco del CAMPO en gris: el canto ENGORDA de uno a dos
   píxeles y no hay halo. Color y grosor a la vez, como en el campo (M-6), para que
   el foco no dependa sólo de distinguir el gris del filete. */
.bf-cred:focus-within {
  border-color: var(--bf-muted);
  box-shadow: inset 0 0 0 1px var(--bf-muted);
}

/* ── Casilla, interruptor y segmentado ─────────────────────────────────────── */

.bf-check {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: var(--bf-table-row-font-size);
}

/* ── LA CASILLA (chore 90.256 · contrato §3 · S-8) ─────────────────────────────
   Marcada NO se rellena de naranja. Una casilla llena del color de marca es un
   botón encendido: en una tabla de veinte filas, cinco marcadas eran cinco
   bloques naranjas gritando por encima de los nombres, que es lo que se lee. Lo
   que cambia al marcar es que APARECE LA MARCA, en el color del texto, y la caja
   se queda donde estaba. Un ajuste no es un estado de planta: ni verde ni ámbar,
   y tampoco un relleno que pese como una acción.

   UNA SOLA PIEZA para las tres caras que tiene la casilla en el catálogo:
     · el DIBUJO (`.bf-box`), que es lo que pinta el árbol de permisos;
     · la casilla REAL de una etiqueta (`.bf-check`) y la de la columna de
       selección de una tabla (chore 90.117: ahí dentro hay un `input` de verdad
       para que la selección por lotes funcione sin JavaScript). Se dibuja SOBRE
       el `input` (`appearance: none`), no encima de uno escondido: el teclado,
       el espacio, el foco y lo que anuncia el lector de pantalla siguen siendo
       del navegador. `accent-color` no valía: tiñe el relleno y nada más, que es
       justo lo que sobra.
   El interruptor también es un `input[type=checkbox]` dentro de `.bf-check`, y
   por eso se aparta con `:not(.bf-switch)`: sin él, esta regla le pondría la
   marca encima del pulgar.

   El FILETE es el de mando (M-12, --bf-field-line), en reposo y marcada: el
   sistema de origen lo aclara en reposo para que veinte casillas vacías no
   dibujen veinte recuadros, pero aquí manda el 3:1 del límite de un control.

   `inline-grid` no es adorno (chore 90.117): fuera de un contenedor flex —en una
   celda de tabla— un `<i>` en línea colapsa a cero de alto y la casilla
   desaparece. `vertical-align: middle` la centra con el renglón de al lado. */
.bf-box,
.bf-check input[type="checkbox"]:not(.bf-switch),
.bf-table th.ck input[type="checkbox"],
.bf-table td.ck input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  display: inline-grid;
  place-content: center;
  vertical-align: middle;
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0;
  box-sizing: border-box;
  /* El filete de la casilla de Hugin: el apagado al 32 %, al 55 % marcada (abajo). */
  border: 1px solid color-mix(in srgb, var(--bf-muted) 32%, transparent);
  border-radius: var(--bf-radius-input);
  background-color: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  transition

/* Marcada o a medias, el filete sube al 55 % (Hugin). Y con el foco, sólo el aro de
   acento: la sombra de tinta del foco global en una caja de 18 px era un segundo
   marco. */
.bf-check input[type="checkbox"]:not(.bf-switch):is(:checked, :indeterminate),
.bf-table th.ck input[type="checkbox"]:is(:checked, :indeterminate),
.bf-table td.ck input[type="checkbox"]:is(:checked, :indeterminate) {
  border-color: color-mix(in srgb, var(--bf-muted) 55%, transparent);
}

.bf-check input[type="checkbox"]:not(.bf-switch):focus-visible,
.bf-table th.ck input[type="checkbox"]:focus-visible,
.bf-table td.ck input[type="checkbox"]:focus-visible {
  box-shadow: none;
}:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    border-color var(--bf-dur-fast) var(--bf-ease-out);
}

/* La marca es una MÁSCARA pintada con `currentColor` (tokens.css): un trazado para
   los dos temas. Nace encogida y transparente, y al marcar crece a su sitio. */
.bf-box::after,
.bf-check input[type="checkbox"]:not(.bf-switch)::after,
.bf-table th.ck input[type="checkbox"]::after,
.bf-table td.ck input[type="checkbox"]::after {
  content: "";
  width: 0.85rem;
  height: 0.85rem;
  background-color: currentColor;
  -webkit-mask: var(--bf-check-mark) center / contain no-repeat;
  mask: var(--bf-check-mark) center / contain no-repeat;
  opacity: 0;
  transform: scale(0.55);
  transition:
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    transform var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-check input[type="checkbox"]:not(.bf-switch),
.bf-table th.ck input[type="checkbox"],
.bf-table td.ck input[type="checkbox"] {
  cursor: pointer;
}

/* El hover, sólo FONDO (S-5): un canto teñido al apuntar se lee como estado
   —marcada, con foco, con error— y no como «estás encima». */
.bf-check input[type="checkbox"]:not(.bf-switch):hover:not(:disabled),
.bf-table th.ck input[type="checkbox"]:hover:not(:disabled),
.bf-table td.ck input[type="checkbox"]:hover:not(:disabled) {
  background-color: var(--bf-hover);
}

.bf-box.on::after,
.bf-box.eff::after,
.bf-box.mix::after,
.bf-check input[type="checkbox"]:not(.bf-switch):is(:checked, :indeterminate)::after,
.bf-table th.ck input[type="checkbox"]:is(:checked, :indeterminate)::after,
.bf-table td.ck input[type="checkbox"]:is(:checked, :indeterminate)::after {
  opacity: 1;
  transform: none;
}

/* Los otros dos estados del árbol de permisos (chore 90.117). Sin ellos la
   casilla sólo sabe decir sí o no, y el árbol MIENTE en los dos casos en los que
   la respuesta no es ninguna de las dos. La semántica es la de siempre; lo que
   cambia (chore 90.256) es cómo se pinta, ahora que «concedido» ya no es un
   relleno naranja del que distinguirse:

     .eff — EFECTIVO por herencia: lo cubre su módulo, no se concedió aquí. Se ve
            la marca, pero en gris, sobre el HUNDIDO y con el filete DISCONTINUO
            —el mismo que dice «esto lo gestiona otro» en un campo congelado—:
            quitarlo no se hace desde esta fila. La diferencia con «concedido» NO
            puede ser sólo el gris: en claro, la tinta apagada y la del texto
            quedan cerca. La dicen la línea discontinua y el fondo.
     .mix — el módulo está A MEDIAS: unos permisos sí y otros no. La raya es la
            forma universal del indeterminado, y va en la tinta del texto como la
            marca: es una decisión propia. */
.bf-box.eff {
  background-color: var(--bf-panel-bg);
  border-style: dashed;
  color: var(--bf-muted);
}

.bf-box.mix::after,
.bf-check input[type="checkbox"]:not(.bf-switch):indeterminate::after,
.bf-table th.ck input[type="checkbox"]:indeterminate::after,
.bf-table td.ck input[type="checkbox"]:indeterminate::after {
  -webkit-mask-image: var(--bf-check-dash);
  mask-image: var(--bf-check-dash);
}

.bf-check input[type="checkbox"]:not(.bf-switch):disabled,
.bf-table th.ck input[type="checkbox"]:disabled,
.bf-table td.ck input[type="checkbox"]:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

/* El botón de radio se queda con el del navegador: con `accent-color` el punto
   sale en naranja, que S-8 admite para un ajuste. */
.bf-check input[type="radio"] {
  width: 17px;
  height: 17px;
  margin: 0;
  flex: none;
  accent-color: var(--bf-accent);
}

/* ── EL INTERRUPTOR, EL DE HUGIN (chore 90.256; rediseño general) ──────────────
   El de Hugin y Latanur tal cual (reconocimiento de Latanur §A.4.2): 2,05 × 1,25 rem, carril SIN
   filete —su canto es la sombra interior— del color del campo, pulgar oscuro de 0,95 rem a 0,125
   del canto con su sombra, recorrido de 0,8 rem. El naranja (--bf-cta, el de acción) crece desde
   la izquierda y el pulgar llega, suaves (sin rebote desde la chore 90.960); mientras se aprieta, se estira. Las
   dos piezas son pseudoelementos porque el interruptor es UN elemento: el `<i>` que dibuja el
   catálogo o el `input[type=checkbox]` de verdad (chore 90.080). Hasta el rediseño llevaba el
   filete de mando (M-12) y un pulgar claro con borde: el usuario los pidió como los de Hugin. */
.bf-switch,
input.bf-switch {
  appearance: none;
  -webkit-appearance: none;
  position: relative;
  display: inline-block;
  vertical-align: middle;
  flex: none;
  width: 2.05rem;
  height: 1.25rem;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  border: 0;
  border-radius: 999px;
  background: var(--bf-switch-track);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 22%, transparent);
}

input.bf-switch {
  cursor: pointer;
}

/* El relleno del CTA crece desde la izquierda, suave (260 ms; sin rebote desde la chore 90.960), y aparece en 130. */
.bf-switch::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  background: var(--bf-cta);
  opacity: 0;
  transform: scale(0.4);
  transform-origin: left center;
  transition:
    opacity 130ms var(--bf-ease-out),
    transform 260ms cubic-bezier(0.45, 0, 0.25, 1);
}

/* El pulgar: oscuro, sin filete y con su sombra; se desliza suave (280 ms; sin rebote desde la chore 90.960). */
.bf-switch::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.125rem;
  width: 0.95rem;
  height: 0.95rem;
  border-radius: 999px;
  background: var(--bf-switch-thumb);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--bf-shadow-ink) 35%, transparent);
  transform: translateY(-50%);
  transition:
    transform 280ms cubic-bezier(0.45, 0, 0.25, 1),
    width 180ms var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-switch.on::before,
input.bf-switch:checked::before {
  opacity: 1;
  transform: none;
}

.bf-switch.on::after,
input.bf-switch:checked::after {
  transform: translate(0.8rem, -50%);
}

/* EL ESTIRÓN: mientras se aprieta, el pulgar se alarga y parece tener peso. Encendido crece hacia la
   izquierda, y por eso el recorrido apretado es 0,625 rem: el canto que da al tope se queda clavado.
   Sólo en el control real: el dibujo no se aprieta. */
input.bf-switch:active:not(:disabled)::after {
  width: 1.21rem;
}

input.bf-switch:checked:active:not(:disabled)::after {
  transform: translate(0.625rem, -50%);
}

input.bf-switch:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

input.bf-switch:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* ── EL SEGMENTADO (chore 90.256 · contrato §3 y §6, M-9) ──────────────────────
   Elegir UNO de pocos valores. El carril es el velo de campo y el segmento
   elegido va EN EL VELO DE SELECCIONADO (--bf-selected) con la tinta de marca.
   Antes era la pastilla naranja sólida del «aquí estás» del menú, y un
   conmutador no es un botón primario: el fondo sólo tiene que decir dónde acaba
   el segmento, y el mensaje de lejos lo lleva la tinta.

   La tinta es la de marca LEGIBLE (--bf-selected-ink) y no el naranja gráfico:
   S-7 pide 4,5:1 a la letra, y #ee7b24 da 2,8:1 sobre el velo en claro. En claro
   vale --bf-accent-text; en oscuro el velo de seleccionado aclara el fondo lo
   bastante como para que ni ese llegue, y el token sube hacia la tinta del texto
   (tokens.css).

   Nota para el sistema de origen: su hoja de traslado (decisión T1) dejaba el
   segmentado en naranja sólido; se sigue el contrato (§3 y §6), que es lo
   normativo y el de alcance mayor. */
/* Chore 90.649: TODOS los conmutadores son el mismo, el carril de pastilla de `.bf-tabs` (90.457) —el de Productos e
   Instancias—: carril de velo SIN filete, opciones en píldora con su icono delante y la puesta con el tinte mínimo de
   marca y la sombra de pastilla. Antes convivían dos mandos para el mismo gesto (la caja con filete y separadores de
   aquí y la pastilla de allá) y el usuario los pidió iguales. La tinta sigue siendo la legible (S-7). */
.bf-seg {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 0.125rem;
  max-width: 100%;
  box-sizing: border-box;
  padding: 0.25rem;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-bg) 45%, transparent);
}

.bf-seg i,
.bf-seg button,
.bf-seg a,
.bf-seg label {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  font-style: normal;
  font-size: var(--bf-text-ui);
  font-family: inherit;
  padding: 0.375rem 0.75rem;
  color: var(--bf-muted);
  background: transparent;
  border: 0;
  border-radius: 999px;
  white-space: nowrap;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

/* Cada opción con su icono delante, como las pestañas: se reconoce sin leerla. */
.bf-seg :is(i, button, a, label) > svg {
  flex: none;
  width: 15px;
  height: 15px;
}

/* Un segmento con destino es un enlace, pero se lee como un botón: `a:hover` no
   puede subrayarlo (chore 90.215). El estado activo lo dice `.on`, no la raya. */
.bf-seg a:hover {
  text-decoration: none;
}

/* El segmentado como CONTROL de formulario (chore 90.070 · U-12): cada opción es
   un botón de radio de verdad dentro de su etiqueta. Se elige con el teclado, lo
   anuncia un lector de pantalla y el formulario lo envía sin JavaScript; el radio
   no se pinta —lo pinta el segmento—, pero sigue estando y sigue siendo enfocable
   (nada de display:none, que lo sacaría del orden de tabulación). */
.bf-seg label {
  position: relative;
}

.bf-seg label input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

/* El hover toca la SUPERFICIE (S-5): un peldaño de velo y la tinta del texto. */
.bf-seg :where(i, button, a, label):hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

/* El foco del teclado tiene que verse en el SEGMENTO, porque el radio no se ve.
   Y como quien tiene el foco es el radio —invisible—, la línea de tinta del
   anillo doble (90.147) no le llega desde el reset: hay que ponérsela aquí. */
.bf-seg label:focus-within {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 1px var(--bf-surface-fg);
}

/* El elegido: la pastilla de las pestañas. Sin hover propio —lo puesto no cambia
   al apuntarlo—, y por eso esta regla va DESPUÉS de la del hover. */
.bf-seg i.on,
.bf-seg button.on,
.bf-seg a.on,
.bf-seg [aria-pressed="true"],
.bf-seg [aria-current="true"],
.bf-seg label:has(input:checked) {
  background-color: color-mix(in srgb, var(--bf-accent) 6%, var(--bf-surface-bg));
  box-shadow: var(--bf-pill-shadow);
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-seg :is(i.on, button.on, a.on, [aria-pressed="true"], [aria-current="true"], label:has(input:checked)) > svg {
  color: var(--bf-selected-ink);
}

/* ── Elegir un icono del shell (contrato 00.004 1.15.0) ─────────────────────
   La ficha de instancia elige su icono de entre el VOCABULARIO del shell: son
   nombres, y para que quien elige vea lo que elige cada opción lleva el mismo
   dibujo que pintará el shell (Ui/BifrostShellGlyphs, copiado de shell.ts). Es
   el segmentado como control de formulario, pero en rejilla —treinta y tantas
   opciones no caben en una fila—: cada opción es un botón de radio de verdad
   dentro de su etiqueta, que no se pinta pero sigue siendo enfocable, así que
   se elige con el teclado (las flechas recorren el grupo) y viaja sin JS.
   Cada opción es un CONTROL: filete de campo (M-12) y radio de campo; el hover
   toca la superficie (S-5); el foco se ve en la opción, como en el segmentado;
   y la elegida es el velo de seleccionado con tinta de marca (M-9), no un
   relleno naranja. La rejilla nace a dos columnas y ensancha sola. */
.bf-iconpick {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(8.5rem, 1fr));
  gap: 6px;
}

.bf-iconpick label {
  position: relative;
  display: flex;
  align-items: center;
  gap: 8px;
  min-height: 36px;
  padding: 6px 10px;
  border: 1px solid var(--bf-field-line);
  border-radius: var(--bf-field-radius);
  background-color: var(--bf-field-bg);
  color: var(--bf-muted);
  font-size: 12.5px;
  line-height: 1.2;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-iconpick label input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

.bf-iconpick label > svg,
.bf-iconpick label > .ini {
  flex: none;
  width: 18px;
  height: 18px;
}

/* «El del producto» cuando el producto no declara icono: la inicial, que es lo
   que pinta el shell. Una muesca con la letra, en el mismo cuadro del dibujo. */
.bf-iconpick .ini {
  display: inline-grid;
  place-items: center;
  border: 1px dashed currentColor;
  border-radius: var(--bf-radius-xs);
  font-size: 10.5px;
  font-weight: 600;
  font-style: normal;
}

/* La primera opción no es un icono sino la herencia: se lleva el renglón y dice
   debajo qué hereda, para que «sin icono propio» no se lea como «sin icono». */
.bf-iconpick label.inherit {
  grid-column: 1 / -1;
}

.bf-iconpick label.inherit small {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  font-weight: 400;
  color: var(--bf-muted);
}

.bf-iconpick label:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-iconpick label:focus-within {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 1px var(--bf-surface-fg);
}

/* La elegida, después del hover para ganarle a igual especificidad: lo puesto no
   cambia al apuntarlo. */
.bf-iconpick label:has(input:checked) {
  background-color: var(--bf-selected);
  border-color: var(--bf-selected-ink);
  color: var(--bf-selected-ink);
  font-weight: 600;
}

/* El error (F-8) en el filete de las opciones, como en un campo. */
.bf-iconpick.err label {
  border-color: var(--bf-danger);
}

/* ═══ 3 · Superficies ════════════════════════════════════════════════════════ */

/* LA TARJETA ES UN VELO (chore 90.257 · contrato §3 · S-3). Era una superficie
   maciza; ahora es --bf-card-veil —la tarjeta al 54 %— y la dibuja su CANTO. M-5
   sigue en pie como principio: una superficie por tarjeta, y cabecera y pie se
   separan sólo con filete. Lo que cambia es el material, no la regla.
   Las piezas propias de Bifrost que también son tarjeta (la de instancia, la de
   producto del portal y la del catálogo) se visten con el mismo velo. */
.bf-card {
  background-color: var(--bf-card-veil);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
  /* El cristal de la caja del inicio de sesión, en claro (chore 90.611): desenfoque, bisel
     y sombra cálida por token; en oscuro los tres valen nada y la tarjeta queda como era. */
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  box-shadow: var(--bf-card-glass-bevel), var(--bf-card-glass-drop);
  overflow: hidden;
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

/* LA QUE SE ABRE sube dos píxeles al apuntarla (Hugin, `.card[data-open]`): el
   velo se cierra un punto, el canto se marca y asoma la sombra corta. Dos y no
   tres: con más, una rejilla entera parece moverse al pasar el ratón.
   SÓLO la que se abre entera —un enlace, o [data-open]—. Una tarjeta con un
   formulario o con su propio botón dentro no se levanta: prometería un clic que
   su cuerpo no cumple. (La del alta, .bf-newcard, se retiró en la chore 90.456: el alta va sólo arriba.)
   La sombra va en la PROPIA tarjeta y no en un ::before como en Hugin: aquí la
   tarjeta recorta (`overflow: hidden`, por las esquinas de la tabla de dentro) y
   se comería la sombra de un hijo. Con tan pocas tarjetas que se abran, animar
   `box-shadow` no pesa. Con movimiento reducido las duraciones caen a 1 ms: sube,
   pero sin viaje. */
.bf-card[data-open],
a.bf-card[href] {
  transition:
    transform var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-card[data-open]:hover,
a.bf-card[href]:hover {
  transform: translateY(-2px);
  border-color: var(--bf-card-edge-hover);
  background-color: var(--bf-card-veil-hover);
  box-shadow: var(--bf-card-glass-bevel), var(--bf-card-lift);
  text-decoration: none;
}

/* Pulsada, se hunde: el gesto se cierra donde empezó. */
.bf-card[data-open]:active,
a.bf-card[href]:active {
  transform: translateY(0);
  transition-duration: var(--bf-dur-fast);
}

.bf-card > header {
  padding: 14px 18px;
  border-bottom: 1px solid var(--bf-border);
  display: flex;
  align-items: center;
  gap: 12px;
}

.bf-card > header h3 {
  font-size: var(--bf-panel-title-font-size);
  font-weight: 600;
}

.bf-card > header .aside {
  margin-left: auto;
  font-size: var(--bf-legend-font-size);
  color: var(--bf-dim);
}

.bf-card .in {
  padding: var(--bf-panel-padding);
}

/* Ritmo vertical dentro de la caja de una tarjeta, PIEZA A PIEZA (chore 90.160). `.in` es sólo relleno
   —caja de bloque, sin flex ni gap— y el reset pone `p { margin: 0 }`, así que lo que se apile ahí sale
   pegado salvo que la pieza traiga su propio hueco. Pasa en el estado ACTIVO de la verificación en dos
   pasos (la explicación contra el botón de desactivar) y en las dos tarjetas de su lateral.
   NO se le da el ritmo a TODAS las cajas, y no por pereza: son 33 en el árbol y unas 20 apilan varios
   hermanos —seis de ellas son fichas de administración, cuyos campos ya traen su propio hueco, y otras
   tantas vistas de auditoría—. Ensancharlas a ciegas, sin ver ninguna pintada, sería un destrozo mayor
   que el arreglo. Quien necesita el ritmo lo PIDE, y así se ve en el marcado quién lo lleva. */
.bf-card .in.stack > * + * {
  margin-top: 16px;
}

.bf-toolbar {
  display: flex;
  align-items: center;
  gap: 9px;
  row-gap: 8px;
  flex: none;
  flex-wrap: wrap;
}

.bf-toolbar .count {
  margin-left: auto;
  font-size: 12.5px;
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

/* La pastilla de filtro es un MANDO y lleva su filete (M-12 · chore 90.255). Iba
   con el filete de superficie (--bf-border, 1,3:1): en la barra se leía como una
   etiqueta y no como algo que se pulsa, que es lo que es —abre un desplegable,
   cambia la lista o suelta un filtro—. Lleva el alto único de mando (S-11), porque
   comparte fila con el buscador y los botones, y el relleno opaco del control: la
   mayoría lleva un <select> dentro, y la lista del sistema se pinta con el fondo
   del control. */
.bf-filter {
  height: var(--bf-control-height);
  border-radius: var(--bf-radius-btn);
  border: 1px solid var(--bf-field-line); /* M-12 */
  background-color: var(--bf-control-bg);
  font-size: 12.5px;
  font-family: inherit;
  color: var(--bf-muted);
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 0 11px;
  cursor: pointer;
  text-decoration: none;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

/* El realce, en la superficie y no en el canto (S-5). El filtro puesto no lo
   cambia: su fondo ya dice que está puesto. */
.bf-filter:not(.on):hover {
  background-color: var(--bf-control-hover-bg);
}

/* Los que SON el campo (auditoría: fechas, IP) llevan el radio del campo, y los
   que son el desplegable, su flecha propia y su hueco. */
/* Y los dos van SIN CANTO (chore 90.399), como el campo y el desplegable de Hugin
   (`.input-page`, `.select`): el campo con el velo del campo, el desplegable con su
   opaco y su flecha. El borde se apaga, no se quita: foco y error lo encienden. */
input.bf-filter {
  border-color: transparent;
  border-radius: var(--bf-field-radius);
  background-color: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  cursor: text;
}

input.bf-filter::placeholder {
  color: var(--bf-muted);
}

select.bf-filter {
  border-color: transparent;
  padding-right: 2rem;
  background-image: var(--bf-select-arrow);
  background-repeat: no-repeat;
  background-position: right 0.55rem center;
  background-size: 0.75rem;
  appearance: none;
}

/* El filtro con destino se emite como <a> (AdminFilterTagHelper) y sigue siendo una
   pastilla: nada de subrayado al pasar por encima (chore 90.215). */
a.bf-filter:hover {
  text-decoration: none;
}

.bf-filter b {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

/* Puesto: el fondo y la tinta de acento dicen que está actuando. El canto va en la
   TINTA de acento y no en el naranja de marca, que en claro da 2,8:1 y dejaba
   justo al filtro puesto —el que más importa ver— por debajo del 3:1 de M-12. */
.bf-filter.on {
  border-color: var(--bf-accent-text);
  color: var(--bf-accent-text);
  background-color: var(--bf-accent-soft);
}

/* Filtro con valores para elegir (chore 90.072). La maqueta dibuja el filtro como
   una pastilla de solo lectura —«Estado activos»—; una pantalla servida necesita
   además cambiarlo, y eso no se dibuja distinto: el <select> nativo se desviste
   hasta parecer el <b> del valor, conservando su flecha (que es lo que dice que
   se puede cambiar) y todo su comportamiento de teclado y lector de pantalla. */
.bf-filter select {
  border: 0;
  padding: 0 1.1rem 0 0;
  margin: 0;
  background: var(--bf-select-arrow) no-repeat right center / 0.75rem;
  color: var(--bf-surface-fg);
  font-family: inherit;
  font-size: inherit;
  font-weight: 600;
  cursor: pointer;
  max-width: 12rem;
  /* La flecha PROPIA (chore 90.255), la misma del desplegable: la nativa es la que
     delata el tema oscuro a medias. */
  appearance: none;
}

/* La pastilla que ENVUELVE un desplegable es la caja del desplegable, como la
   `.sort-box` de Hugin, que va SIN CANTO por lo mismo que su <select> (chore 90.399).
   La puesta conserva el suyo: ahí el canto es un estado, no un adorno. Con :where
   para no pesar más que `.bf-filter` a secas: el foco y la puesta, que se escriben
   con más peso, le siguen ganando el canto. */
.bf-filter:where(:has(> select)) {
  border-color: transparent;
}

/* Las opciones, con el tema: la lista la dibuja el sistema y no lo hereda. */
.bf-filter select > option,
select.bf-filter > option {
  background-color: var(--bf-control-bg);
  color: var(--bf-surface-fg);
  font-weight: 400;
}

.bf-filter select:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

/* EL FOCO, COMO EL DE UN CAMPO (chore 90.255): canto calibrado y engordado, sin aro.
   En la pastilla que lleva un <select> dentro, el foco lo pinta la PASTILLA, que es
   el mando que se ve: el aro alrededor de la palabra de dentro era un segundo
   contorno dentro del primero. Sólo donde el navegador sabe preguntar por el hijo
   (:has); donde no, se queda el aro de arriba, que se ve igual de bien. */
@supports selector(:has(a)) {
  .bf-filter select:focus-visible {
    outline: none;
    box-shadow: none;
  }

  .bf-filter:has(select:focus-visible) {
    border-color: var(--bf-cta-edge);
    box-shadow: inset 0 0 0 1px var(--bf-cta-edge);
  }
}

input.bf-filter:focus-visible,
select.bf-filter:focus-visible {
  outline: none;
  border-color: var(--bf-cta-edge);
  box-shadow: inset 0 0 0 1px var(--bf-cta-edge);
}

/* Ancho de un buscador suelto en la barra (chore 90.072): hoy sólo el de IP de la
   auditoría, que es una pastilla de filtro y no la pieza de abajo. Mobile-first
   (MF-2): a lo ancho por defecto, que es lo que cabe en un teléfono, y desde 620
   el ancho propio, con los filtros a su lado. */
.bf-toolbar input[type="search"] {
  width: 100%;
}

@media (min-width: 620px) {
  .bf-toolbar input[type="search"] {
    width: 280px;
  }
}

/* ── Buscador (.bf-field-search · chore 90.255) ──────────────────────────────
   La caja de BUSCAR de las barras de filtros: lupa, campo y un aspa que sólo sale
   cuando hay algo que limpiar —un botón que no hace nada es ruido—. La emite
   <bf-field-search> y el aspa la enseña ts/ui/filters.ts: sin JavaScript no
   tendría con qué vaciar el campo, así que sale con `hidden`.
   Comparte fila con los filtros y los botones, así que lleva su alto (S-11) y no
   uno propio: una caja más alta que sus vecinas se lee como un descuadre.
   El buscador es la CAJA, no el campo: el velo, el filete, el hover y el foco los
   pinta ella, con la misma receta que un campo, y el <input> de dentro va desnudo;
   si pintara el suyo, saldrían dos cantos pegados. Mobile-first (MF-2): a todo lo
   ancho en un teléfono y desde 620 su ancho, con los filtros a su lado. */
.bf-field-search {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  height: var(--bf-control-height);
  padding: 0 0.375rem 0 0.625rem;
  /* SIN CANTO, como el buscador de Hugin (`.field-search`): el velo ya dice que ahí se
     escribe. El borde se APAGA y no se quita, para que el error —que sí es un estado
     con canto— no mueva la maqueta al encenderlo. */
  border: 1px solid transparent;
  border-radius: var(--bf-field-radius);
  background-color: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out);
}

@media (min-width: 620px) {
  .bf-field-search {
    width: 17.5rem;
  }
}

/* Como en Hugin: al apuntar, el velo de apuntar EN LUGAR del del campo; y al escribir,
   un fondo un peldaño más claro y NINGÚN canto —en un campo que se pulsa para escribir,
   un aro naranja es lo que más pesa de la fila—. */
.bf-field-search:hover:not(:focus-within) {
  background-color: var(--bf-hover);
}

.bf-field-search:focus-within {
  background-color: var(--bf-field-search-focus-bg);
}

.bf-field-search > svg {
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
  color: var(--bf-muted);
}

.bf-field-search > input {
  flex: 1;
  min-width: 0;
  /* Llena el alto de la caja, también cuando crece en táctil: toda ella es campo. */
  align-self: stretch;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: var(--bf-field-font-size);
  /* Ni aro ni la línea de tinta del foco global: el foco lo pinta la caja. */
  outline: none;
  box-shadow: none;
}

.bf-field-search > input::placeholder {
  color: var(--bf-muted);
}

/* La cruz nativa de <input type="search"> sólo la pinta WebKit, y en oscuro sale de
   un gris que no es ninguno de los nuestros: se apaga y se pone la propia. */
.bf-field-search > input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  display: none;
}

.bf-field-search > button {
  display: none;
}

/* ENTRA, no aparece: nace justo al teclear la primera letra, y sin la entrada se
   lee como un salto del campo. */
@keyframes bf-field-clear-in {
  from {
    opacity: 0;
    transform: scale(0.6);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.bf-field-search[data-filled="true"] > button {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: 1.5rem;
  height: 1.5rem;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
  animation: bf-field-clear-in var(--bf-dur-base) var(--bf-ease-out);
}

.bf-field-search > button:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-field-search > button > svg {
  width: 0.75rem;
  height: 0.75rem;
}

/* Un error en la caja se pinta en la caja, como el foco. */
.bf-field-search:has(> input[aria-invalid="true"]) {
  border-color: var(--bf-danger);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-danger) 22%, transparent);
  animation: bf-field-invalid-in var(--bf-dur-slow) var(--bf-ease-out);
}

/* ── Tabla ─────────────────────────────────────────────────────────────────── */

.bf-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  font-size: var(--bf-table-row-font-size);
}

.bf-table th {
  text-align: left;
  font-size: var(--bf-table-header-font-size);
  /* Como la tabla de Hugin (editor-inspector.css · .editor-table th, chore 90.389):
     versalitas con 0.04em, no 0.1em —con más aire el rótulo se lee letra a letra—,
     y el mismo relleno apretado que sus filas.
     Tinta APAGADA y no tenue, y 12 px a los lados: los de Latanur (`.datatable thead
     th`, .5rem .75rem; chore 90.394). */
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bf-muted);
  font-weight: 600;
  padding: 8px 12px;
  /* Fino y discreto (chore 90.664): medio pelo en la tinta de los filetes de fila, no la fuerte. */
  border-bottom: 0.5px solid color-mix(in srgb, var(--bf-border) 80%, transparent);
  white-space: nowrap;
}

/* Fila de Hugin (manage.ts · py-1.5 px-2): 6 px arriba y abajo, no 10. El filete va
   flojo, al 45 % del pelo: en una tabla de treinta filas, treinta rayas al mismo peso
   que el texto se leen antes que el texto.
   Chore 90.394: la fila de Latanur (`px-3 py-1.5 border-b bd`): 12 px a los lados y
   el filete de superficie, que ya es el pelo flojo que aquí se buscaba mezclando. */
.bf-table td {
  padding: 6px 12px;
  border-bottom: 1px solid var(--bf-border);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-table tr:last-child td {
  border-bottom: 0;
}

.bf-table td.sub {
  color: var(--bf-muted);
}

/* La marca de la sesión RECORDADA en el visor de sesiones (chore 90.105): cuelga
   del navegador, igual que en «Mis sesiones», para que la misma cosa se diga en
   el mismo sitio en las dos pantallas. */
.bf-table td .bf-kept {
  display: block;
  font-size: 11.5px;
  color: var(--bf-dim);
  margin-top: 2px;
}

.bf-table td.right,
.bf-table th.right {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* ── La tabla RUEDA POR DENTRO y su cabecera se queda (chore 90.259 · contrato §3) ──
   Una tabla de veinticinco filas se rueda entera y, a media lista, no hay forma de
   saber qué columna es «Caduca» y cuál «Estado»: había que subir a leer los rótulos
   y volver a bajar. Pegada arriba y de cristal, las filas pasan por debajo y se
   sigue viendo qué es cada columna.

   EL ENVOLTORIO ES DE LA TABLA (MF-5): lo que se desplaza cuando la tabla no cabe
   es él, no la página ni el chasis. Base = teléfono: sólo rueda a lo ancho, y a lo
   largo rueda la PÁGINA —un desplazamiento dentro de otro atrapa el gesto táctil, y
   en un teléfono la tabla sería una ventanita dentro de la pantalla—.

   Desde 620 el envoltorio tiene un alto ACOTADO y rueda en los dos ejes. Es lo que
   hace que la cabecera se pueda pegar: `sticky` mide contra su contenedor de
   desplazamiento más cercano, y ése ya era este envoltorio —`overflow-x: auto` lo
   convierte en contenedor en los DOS ejes—, así que con la hoja entera rodando la
   fila pegajosa se quedaba pegada a una caja que no se mueve. El alto es la ventana
   menos el cromo (--bf-table-chrome, medido en tokens.css), así que el pie con el
   paginador se queda SIEMPRE a la vista debajo; y nunca menos que el suelo, para
   que una ventana baja no deje una rendija de tres filas. `overscroll-behavior`
   para que llegar al final de la tabla no se lleve la página por delante.

   Y las barras, finas y con tinta del tema: ahora que la tabla rueda por dentro se
   ven siempre, y la del sistema sigue a la preferencia del SISTEMA (color-scheme)
   y no al tema elegido en Bifrost, así que en oscuro salía una barra blanca de
   lado a lado de la tabla. */
.bf-table-wrap {
  overflow-x: auto;
}

@media (min-width: 620px) {
  .bf-table-wrap {
    /* MF-6: dvh con respaldo. Van escritos aquí y no dentro del token porque una
       custom property con dvh que el navegador no entiende no cae a la línea de
       arriba: invalida la declaración entera y el envoltorio se quedaría sin tope. */
    max-height: max(var(--bf-table-floor), calc(100vh - var(--bf-table-chrome)));
    max-height: max(var(--bf-table-floor), calc(100dvh - var(--bf-table-chrome)));
    overflow: auto;
    overscroll-behavior: contain;
  }

  /* LA CABECERA, de cristal y no maciza: sobre una tabla, una banda opaca corta la
     lista en dos; con el velo y el desenfoque se lee que lo de debajo sigue siendo
     la misma tabla. Los dos tokens se calculan desde el velo de la tarjeta y pasan a
     macizo sin cristal (tokens.css): aquí no se repite ninguna consulta.

     Lo que se pega es la SECCIÓN entera, no cada celda, y está medido en Chromium:
     con el cristal en las <th> pegajosas cada celda desenfoca SU trozo por separado
     y en la frontera de columna queda una costura de un píxel (223 de luminancia
     sobre 254 donde pasa letra por debajo: una rejilla vertical que la cabecera no
     tiene); sin el desenfoque desaparece, así que es del cristal por celda. El <thead>
     pegajoso pinta un solo velo y un solo desenfoque de punta a punta, sin costuras,
     y las filas pasan por debajo desenfocadas.

     El filete de abajo va PINTADO EN EL FONDO de la celda, no como borde: con
     `border-collapse: collapse` el borde lo pinta la tabla en su sitio original, así
     que al rodar se quedaba atrás (medido: un borde de 3 px desaparece de la cabecera
     pegada) y la cabecera flotaba sin línea. Tampoco como sombra interior, que era lo
     obvio: las columnas caen en medios píxeles y el canto suavizado de cada sombra
     dejaba OTRA costura vertical en cada frontera (239-248 sobre 254, medido). Un
     degradado de un píxel viaja con la celda y no tiene cantos. El z-index pone la
     cabecera por encima de lo que las filas traigan posicionado (la marca de una
     casilla, un LED). */
  .bf-table-wrap > table > thead {
    position: sticky;
    top: 0;
    z-index: 2;
    background-color: var(--bf-table-head-veil);
    backdrop-filter: var(--bf-table-head-filter);
    -webkit-backdrop-filter: var(--bf-table-head-filter);
  }

  .bf-table-wrap > table > thead th {
    border-bottom-color: transparent;
    background-image: linear-gradient(to top, color-mix(in srgb, var(--bf-border) 80%, transparent) 0.5px, transparent 0.5px);
  }
}

/* ── LA TABLA LLEGA HASTA ABAJO (chore 90.393) ─────────────────────────────────
   Con pocas filas la tarjeta se quedaba a media vista y el pie con el paginador
   subía con ella. Desde el corte medio la tarjeta que envuelve una tabla ocupa el
   alto disponible: el mismo cálculo que acota el envoltorio (ventana menos el cromo,
   con su suelo) más el alto del pie, que el cromo ya descontaba. El envoltorio crece
   (flex: 1) hasta su tope y rueda por dentro; el pie queda SIEMPRE abajo. Sin tabla
   que llenar, lo que crece es el estado vacío, centrado en el hueco.
   Se engancha por `:has()` y no por clase para no tocar el marcado de la pieza.
   En teléfono, flujo normal: la página rueda y la tarjeta mide lo que su contenido. */
@media (min-width: 620px) {
  .bf-card:has(> .bf-table-wrap) {
    display: flex;
    flex-direction: column;
    min-height: max(var(--bf-table-floor), calc(100vh - var(--bf-table-chrome) + var(--bf-table-foot)));
    min-height: max(var(--bf-table-floor), calc(100dvh - var(--bf-table-chrome) + var(--bf-table-foot)));
  }

  .bf-card:has(> .bf-table-wrap) > .bf-table-wrap {
    flex: 1 1 auto;
    min-height: 0;
  }

  .bf-card:has(> .bf-table-wrap) > .bf-empty {
    flex: 1 1 auto;
  }

  /* El vacío que CUENTA es el que se ve: la auditoría lo lleva siempre en el
     marcado y lo enseña su módulo (chore 90.394). */
  .bf-card:has(> .bf-table-wrap):has(> .bf-empty:not([hidden])) > .bf-table-wrap {
    flex: 0 0 auto;
  }

  .bf-card:has(> .bf-table-wrap) > .bf-foot,
  .bf-card:has(> .bf-table-wrap) > .bf-cursor {
    margin-top: auto;
  }

  /* La banda de filtros DENTRO de la tarjeta (chore 90.394) no crece: es cabecera. */
  .bf-card:has(> .bf-table-wrap) > .bf-toolbar {
    flex: none;
  }
}

/* ── CONTENEDOR SIN CONTENIDO, NO SE PINTA (chore 90.393) ────────────────────────
   Una tarjeta, un cuerpo o un pie que se queda sin nada que enseñar —vacío de
   origen o con todos sus hijos ocultos porque el contenido llega (o no) por JS— no
   pinta su caja: un marco sin nada dentro dice «aquí falta algo» sin decir qué. En
   servidor las vistas ya no emiten el contenedor; esto cubre lo que decide el cliente. */
.bf-card:empty,
.bf-card .in:empty,
.bf-foot:empty,
.bf-card:not(:has(> :not([hidden]))) {
  display: none;
}

/* ── EL LISTADO, COMO LOS MÓDULOS DE LATANUR (chore 90.394) ──────────────────────
   Referencia: Latanur, `Views/Pages/AccessLogs/Index.cshtml` con sus componentes
   `<FilterBox>` y `<DataTable>` y las hojas `components/controls.css` (.filterbox,
   .filterrow) y `components/listing.css` (.card, .datatable). La composición es
   la misma en todos los listados: cabecera de pantalla, y debajo UNA tarjeta con
   la banda de filtros arriba, la tabla que rueda y el pie con el paginador.

   LA BANDA la emite <bf-toolbar action> (AdminToolbarTagHelper) como un
   <details>: cabecera con el embudo, «Filtros», cuántos hay puestos y el galón, y
   los ejes en el cuerpo. Plegar es del propio <details>: sin JavaScript y sin
   guion en línea (CSP).

   UNA SOLA TARJETA. En las vistas la banda es la hermana INMEDIATA de la tarjeta
   que filtra (tabla, árbol o matriz), y aquí se sueldan: la banda pone la tapa
   con su filete de abajo —el `border-bottom` de `.filterbox`— y la tarjeta pierde
   su canto de arriba y el hueco de la pila. Sin tarjeta detrás (la vista de
   tarjetas de Instancias) la banda es tarjeta ella sola. Y cuando va DENTRO de la
   tarjeta (Actividad) es sólo la banda, sin caja propia. */
.bf-toolbar:has(> .bf-filterbox) {
  display: block;
  flex: none;
  background-color: var(--bf-card-veil);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
}

.bf-toolbar:has(> .bf-filterbox):has(+ .bf-card) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.bf-toolbar:has(> .bf-filterbox) + .bf-card {
  margin-top: 0;
  border-top: 0;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.bf-card > .bf-toolbar:has(> .bf-filterbox) {
  border: 0;
  border-bottom: 1px solid var(--bf-border);
  border-radius: 0;
  background: none;
}

/* El plegado se ANIMA donde el navegador sabe (Latanur, `::details-content`); donde
   no, se abre de golpe, que es lo que hace un <details>. */
.bf-filterbox {
  interpolate-size: allow-keywords;
}

.bf-filterbox::details-content {
  block-size: 0;
  overflow-y: clip;
  transition:
    block-size var(--bf-dur-base) var(--bf-ease-out),
    content-visibility var(--bf-dur-base) var(--bf-ease-out);
  transition-behavior: allow-discrete;
}

.bf-filterbox[open]::details-content {
  block-size: auto;
}

.bf-filterbox-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 0.75rem;
  cursor: pointer;
  font-size: var(--bf-text-ui);
  font-weight: 600;
  color: var(--bf-muted);
  list-style: none;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filterbox-head::-webkit-details-marker {
  display: none;
}

.bf-filterbox-head::marker {
  content: "";
}

.bf-filterbox-head:hover {
  background-color: color-mix(in srgb, var(--bf-muted) 7%, transparent);
}

.bf-filterbox-head:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-filterbox-head > svg,
.bf-filterbox-chev > svg {
  width: 15px;
  height: 15px;
  flex: none;
}

/* Cuántos hay puestos, pegado al rótulo: la píldora de acento flojo de Latanur. */
.bf-filterbox-count {
  display: inline-grid;
  place-content: center;
  min-width: 1.25rem;
  height: 1.25rem;
  padding: 0 0.375rem;
  border-radius: var(--bf-radius-lg);
  font-size: var(--bf-text-mini);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  background-color: color-mix(in srgb, var(--bf-accent) 18%, transparent);
  color: var(--bf-surface-fg);
}

/* El recuento de la lista, al otro extremo y en voz baja: es de la tabla, no del filtro. */
.bf-filterbox-head .count {
  margin-left: 0;
  font-size: var(--bf-text-label);
  font-weight: 400;
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-filterbox-chev {
  display: inline-flex;
  color: var(--bf-muted);
  transition: transform var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-filterbox[open] .bf-filterbox-chev {
  transform: rotate(90deg);
}

/* El cuerpo: los ejes en fila y envolviendo (MF-2: el patrón intrínseco antes que
   una consulta). */
.bf-filterbox-body {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.7rem 1.25rem;
  padding: 0.25rem 0.75rem 0.85rem;
}

@media (min-width: 900px) {
  .bf-filterbox-head {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .bf-filterbox-body {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .bf-cursor {
    padding: 0.75rem 1rem;
  }
}

/* EL EJE, como la `.filterrow` de Latanur: el rótulo en versalitas pequeñas y
   apagadas, y el control a su lado. La pastilla de <bf-filter-select> se desviste
   —el rótulo deja de ser caja— y su <select> pasa a ser el desplegable del sistema:
   alto de mando, opaco, flecha propia y SIN CANTO (90.399). El filtro puesto lo
   dice el canto de acento del control (`.is-filtering` de Latanur). */
.bf-filterbox-body .bf-filter:has(> select),
.bf-filterbox-body .bf-filterrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  height: auto;
  padding: 0;
  border: 0;
  background: none;
  font-size: var(--bf-text-label);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bf-muted);
  cursor: default;
}

.bf-filterbox-body .bf-filter:has(> select):hover,
.bf-filterbox-body .bf-filter:has(select:focus-visible) {
  background: none;
  border-color: transparent;
  box-shadow: none;
}

.bf-filterbox-body .bf-filterrow > .bf-label {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
}

.bf-filterbox-body .bf-filter > select,
.bf-filterbox-body .bf-filterrow > select.bf-filter {
  height: var(--bf-control-height);
  max-width: 16rem;
  margin: 0;
  padding: 0 2rem 0 0.625rem;
  border: 1px solid transparent;
  border-radius: var(--bf-field-radius);
  background: var(--bf-control-bg) var(--bf-select-arrow) no-repeat right 0.6rem center / 0.75rem;
  color: var(--bf-surface-fg);
  font-family: inherit;
  font-size: var(--bf-text-body);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  cursor: pointer;
  appearance: none;
}

.bf-filterbox-body .bf-filter > select:hover,
.bf-filterbox-body .bf-filterrow > select.bf-filter:hover {
  background-color: var(--bf-control-hover-bg);
}

.bf-filterbox-body .bf-filterrow > input.bf-filter {
  font-size: var(--bf-text-body);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
}

.bf-filterbox-body .bf-filter.on > select {
  border-color: var(--bf-accent-text);
}

.bf-filterbox-body .bf-filter > select:focus-visible,
.bf-filterbox-body .bf-filterrow > select.bf-filter:focus-visible {
  outline: none;
  border-color: var(--bf-cta-edge);
  box-shadow: inset 0 0 0 1px var(--bf-cta-edge);
}

/* El buscador, a lo ancho en un teléfono y con el ancho de Latanur (`sm:w-80`)
   desde el corte medio. */
.bf-filterbox-body .bf-field-search {
  flex: 1 1 100%;
}

@media (min-width: 620px) {
  .bf-filterbox-body .bf-field-search {
    flex: 0 1 20rem;
    width: 20rem;
  }
}

/* ── Selección de filas (chore 90.117) ─────────────────────────────────────────
   La columna de casillas de las acciones por lotes: hoy la pide el corte de
   sesiones (90.115), donde sin ella hay que expulsar de una en una. La casilla de
   la CABECERA marca la página visible —no «todas», que serían las 31 de las que
   sólo se ven 9—, la fila marcada se tiñe y el botón de la cabecera de página
   dice CUÁNTAS van («Cerrar las 3 seleccionadas»), nunca un «Aceptar» ciego.

   `overflow: visible` porque la celda hereda el recorte con puntos suspensivos de
   `.bf-table td`, y ahí dentro no hay texto que recortar: hay un control que se
   quedaría cortado. */
.bf-table th.ck,
.bf-table td.ck {
  width: 34px;
  padding: 6px 0 6px 12px;
  overflow: visible;
}

/* Seleccionada como en Hugin (.editor-table tr[data-selected]): tinte flojo del acento
   y la PLECA en el canto izquierdo, que es lo que se ve entre treinta filas. */
.bf-table tr.sel td {
  background: color-mix(in srgb, var(--bf-accent) 10%, transparent);
}

.bf-table tr.sel td:first-child {
  box-shadow: inset 2px 0 0 var(--bf-accent);
}

/* ── La fila entera abre su ficha (F-5 · chore 90.112) ─────────────────────────
   El resaltado al pasar por encima es lo que DICE que la fila es una puerta; sin
   él, pulsarla sería un truco que hay que descubrir. La puerta de verdad sigue
   siendo el enlace de la primera celda y el botón de la columna de acciones: esto
   es un atajo. Bajo pointer grueso no hay «encima», así que no dibuja nada y la
   fila sigue abriéndose igual desde sus enlaces (MF-4). */
/* En Hugin TODA fila apuntada se enciende (manage.ts · hover:bg-hover), no sólo las
   que abren ficha: la fila bajo el puntero es la que se está leyendo. */
.bf-table tbody tr:hover td,
.bf-table.link tbody tr:focus-within td {
  background: var(--bf-hover);
}

.bf-table tbody tr.sel:hover td {
  background: color-mix(in srgb, var(--bf-accent) 14%, transparent);
}

/* ── Columna de acciones de una tabla maestra (F-5 · chore 90.117) ─────────────
   «Editar» abre la ficha y la destructiva pide su modal; CUÁL se ofrece depende
   del estado de la fila —el dado de baja ofrece «Reactivar»— y eso lo decide la
   pantalla. Lo que pone el catálogo es la celda: pegada a la derecha, sin el
   recorte con puntos suspensivos de `.bf-table td` —que aquí cortaría un botón,
   no una palabra— y con sus controles en LÍNEA.

   Cada acción destructiva viaja en su propio `<form>` para funcionar sin
   JavaScript, y un formulario es un bloque: sin esto, dos acciones caerían una
   debajo de otra. La celda NO se hace `display: flex`: dejaría de ser celda de
   tabla y se llevaría por delante el reparto de anchos del `colgroup`. */
.bf-table th.acts,
.bf-table td.acts {
  text-align: right;
  overflow: visible;
  white-space: nowrap;
}

.bf-table td.acts > form,
.bf-table td.acts > a,
.bf-table td.acts > button {
  display: inline-flex;
  vertical-align: middle;
  margin-left: 8px;
}

.bf-table td.acts > :first-child {
  margin-left: 0;
}

/* La celda persona es la PUERTA de la fila (F-5 · chore 90.112): el nombre va enlazado
   para que la ficha se abra con el teclado y sin JavaScript, pero NO se pinta como un
   enlace —la fila entera lleva al mismo sitio, y una columna de nombres en color de
   acento se lee como una lista de avisos, no como una lista de personas—. El subrayado
   aparece al pasar por encima y al enfocar, que es donde el enlace tiene que declararse. */
.bf-table td > a:has(.bf-who) {
  color: inherit;
  text-decoration: none;
  display: block;
}

.bf-table td > a:has(.bf-who):hover,
.bf-table td > a:has(.bf-who):focus-visible {
  text-decoration: underline;
}

/* Celda persona: avatar + nombre + usuario. */
.bf-who {
  display: flex;
  align-items: center;
  gap: 11px;
  min-width: 0;
}

.bf-who b {
  font-weight: 600;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bf-who span {
  display: block;
  font-size: 11.5px;
  color: var(--bf-dim);
}

.bf-av {
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--bf-avatar-bg);
  color: var(--bf-accent-fg);
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
  font-weight: 500;
}

/* El avatar grande de una ficha de persona (chore 90.070): el de «Mi perfil», con
   las iniciales sobre naranja tenue. Sirve igual para las iniciales y para la
   imagen subida, que se recorta al círculo. */
.bf-av.lg {
  width: 60px;
  height: 60px;
  font-size: 22px;
  font-weight: 600;
  background: var(--bf-accent-soft);
  color: var(--bf-accent-text);
  object-fit: cover;
}

/* Talla de la cabecera de expediente (chore 90.117): entre el de la tabla y el de
   «Mi perfil». La variante cuadrada es para lo que NO es una persona —un
   producto, un rol, una instancia—: la forma distingue de un vistazo el registro
   de quien lo usa, sin leer nada. */
.bf-av.md {
  width: 42px;
  height: 42px;
  font-size: 14px;
  font-weight: 600;
  /* Desde la chore 90.285 también es la FOTO de la persona en su expediente: se
     recorta al círculo como la grande, sin deformarse. */
  object-fit: cover;
}

.bf-av.sq {
  border-radius: 12px;
}

/* Pie de tabla: recuento + paginación. Se separa con el filete separador, sin
   relleno propio (M-5). Envuelve (chore 90.259): con los cinco mandos del paginador
   el recuento y las páginas ya no caben en un renglón de teléfono, y es el paginador
   el que baja —a la derecha, por su margen automático—, no el que se sale. */
/* Chore 90.394: el pie de Latanur (`p-3 lg:px-4 border-t bd text-xs text-muted`):
   12 px de relleno, 16 a los lados desde el corte ancho, y el filete de superficie. */
.bf-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 0.75rem;
  border-top: 1px solid var(--bf-border);
  font-size: var(--bf-legend-font-size);
  color: var(--bf-muted);
}

@media (min-width: 900px) {
  .bf-foot {
    padding: 0.75rem 1rem;
  }
}

/* ── EL PAGINADOR: cinco mandos (chore 90.259 · contrato §3) ─────────────────────
   Primera, anterior, el SALTO («página [N] de M»), siguiente y última. La ventana de
   números con puntos suspensivos se va: con 28 páginas dejaba la 14 a siete
   pulsaciones y no decía cuántas había sin contar huecos. Con extremos y salto,
   cualquier página está a una pulsación o a un número tecleado.

   Las flechas miden lo que un mando (S-11: 1,7rem, el renglón de mandos) y llevan
   la piel del botón: velo con el filete de control (M-12) y el realce apilado. El
   extremo sin destino es un <span> sin enlace y se ve APAGADO, con la cara del
   botón deshabilitado: en el borde de la lista, «siguiente» sin más páginas es la
   respuesta a la pregunta que se está haciendo. */
/* Alineadas ARRIBA y no al centro: cuando el total baja debajo del campo (abajo, en
   un teléfono), las flechas se quedan a la altura del campo en vez de a media
   altura de campo y total. */
.bf-page {
  margin-left: auto;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: flex-start;
  gap: 0.375rem;
}

/* Chore 90.394: las flechas son las de Latanur (`btn-ghost` del pie de <DataTable>):
   sin velo ni canto, sólo el realce al apuntarlas. El píxel del canto se queda,
   transparente, para que el alto no cambie. */
.bf-page > a,
.bf-page > span {
  display: inline-grid;
  place-items: center;
  flex-shrink: 0;
  width: var(--bf-control-height);
  height: var(--bf-control-height);
  border: 1px solid transparent;
  border-radius: var(--bf-button-radius);
  background-color: transparent;
  color: var(--bf-muted);
  text-decoration: none;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-page > a:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
  text-decoration: none;
}

/* El extremo sin destino, APAGADO como el botón deshabilitado de Latanur. */
.bf-page > span {
  color: var(--bf-muted);
  opacity: 0.45;
  cursor: not-allowed;
}

.bf-page svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* EL SALTO es un formulario GET con un solo campo: Intro lo envía sin JavaScript, y
   los filtros viajan en campos ocultos. El campo lleva la piel del CAMPO —velo,
   filete de control, radio de campo y el foco en el canto—, y sólo cambia lo que su
   trabajo pide: ancho fijo y cifra centrada, para que no baile al pasar de una a
   dos cifras. Con el color de las flechas se leería como un mando más de la fila, y
   es justo lo que no es: en los otros se pulsa, en éste se escribe.

   EN UN TELÉFONO, las cuatro flechas de 44 px, el campo y sus separaciones de 8
   piden 260 px, y a 360 hay 268: caben, pero no con «de 10» al lado. Por eso la base del
   salto mide lo que su CAMPO (flex-basis) y crece hasta su contenido cuando hay
   sitio: a 390 va todo en un renglón y a 360 el total baja debajo del campo, en vez
   de mandar la última flecha sola a otra línea. El rótulo «Página» es eco del nombre
   accesible del campo y sólo se pinta desde el corte medio, donde sobra el sitio. */
.bf-page .jump {
  display: flex;
  flex: 1 1 3.25rem;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: 0.125rem 0.375rem;
  margin: 0;
  white-space: nowrap;
}

.bf-page .jump .pre {
  display: none;
}

@media (min-width: 620px) {
  .bf-page .jump .pre {
    display: inline;
  }
}

.bf-page .jump input {
  width: 3.25rem;
  height: var(--bf-control-height);
  margin: 0;
  padding: 0 0.25rem;
  border: 1px solid var(--bf-field-edge);
  border-radius: var(--bf-field-radius);
  background: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  font-family: inherit;
  font-size: var(--bf-field-font-size);
  text-align: center;
  /* «appearance: none» no le quita las flechas a Gecko; «textfield» sí, en todos. */
  appearance: textfield;
  -moz-appearance: textfield;
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-page .jump input::-webkit-inner-spin-button,
.bf-page .jump input::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

.bf-page .jump input:hover:not(:focus) {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-page .jump input:focus {
  outline: none;
  border-color: var(--bf-cta-edge);
  box-shadow: inset 0 0 0 1px var(--bf-cta-edge);
}

/* ── Pastillas, LED y claves ───────────────────────────────────────────────── */

/* EL CHIP DE ESTADO (chore 90.257 · contrato §3, `.chip` de Hugin). Era una
   pastilla con filete sobre relleno macizo; ahora es un TINTE del 15 % del color
   del estado, sin canto, con la letra en el token de TEXTO. M-2 sigue mandando
   —el color nunca va solo: LED más texto—; lo que cambia es el material.

   S-7, y por qué importa aquí: el color GRÁFICO (--bf-*) es para el LED y los
   bordes, a 3:1; la letra va con --bf-*-text, a 4,5:1. Medida la letra del chip
   sobre la mesa, la tarjeta, el panel y el hundido (chore 90.257), el peor caso es
   5.33:1 en claro y 4.85:1 en oscuro —éste, el neutro sobre el panel—. Con el
   color gráfico el verde claro daría 2.53-2.81:1 y suspendería: es exactamente lo
   que el token de texto existe para impedir.

   El neutro (sin tono) es el «apagado» de la maqueta: la tinta apagada sobre su
   propio tinte del 16 %, el único que Hugin no pinta con un color de estado. */
.bf-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  height: 1.5rem;
  padding: 0 0.625rem;
  border-radius: 999px;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
  background-color: color-mix(in srgb, var(--bf-muted) 16%, transparent);
  white-space: nowrap;
}

.bf-pill.ok {
  color: var(--bf-success-text);
  background-color: color-mix(in srgb, var(--bf-success) 15%, transparent);
}

.bf-pill.warn {
  color: var(--bf-warning-text);
  background-color: color-mix(in srgb, var(--bf-warning) 15%, transparent);
}

.bf-pill.bad {
  color: var(--bf-danger-text);
  background-color: color-mix(in srgb, var(--bf-danger) 15%, transparent);
}

.bf-pill.info {
  color: var(--bf-info-text);
  background-color: color-mix(in srgb, var(--bf-info) 15%, transparent);
}

/* M-2: el LED nunca va solo —siempre al lado de su texto—. Sin tono propio pinta
   el color del texto que lo acompaña (currentColor); con tono, o dentro de un chip
   con tono, pinta el color GRÁFICO del estado (S-7, chore 90.257): es luz, no
   letra, y la tinta oscura del texto en claro lo dejaba apagado. */
.bf-led {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* Tono del LED cuando la línea NO se tiñe entera: el texto se queda en su color y
   el estado lo lleva sólo el punto (así lo pinta la maqueta en el pie del panel de
   acceso y en el cierre de sesión). `idle` es «no lo sé todavía», no un estado
   bueno ni malo: por eso es el gris de apagado y no un semáforo. */
.bf-led.ok,
.bf-pill.ok > .bf-led {
  color: var(--bf-success);
}

.bf-led.warn,
.bf-pill.warn > .bf-led {
  color: var(--bf-warning);
}

.bf-led.bad,
.bf-pill.bad > .bf-led {
  color: var(--bf-danger);
}

/* Informativo: azul del sistema, ni bueno ni malo (lo usa la severidad de los
   avisos, chore 90.069). Se distingue de `idle`, que es «no lo sé todavía». */
.bf-led.info,
.bf-pill.info > .bf-led {
  color: var(--bf-info);
}

.bf-led.idle {
  color: var(--bf-off);
}

.bf-tagk {
  font-family: var(--bf-font-mono);
  font-size: 11.5px;
  color: var(--bf-muted);
  background: var(--bf-panel-bg);
  border: 1px solid var(--bf-border);
  border-radius: 5px;
  padding: 1px 7px;
}

/* ── Mensajes y vacío ──────────────────────────────────────────────────────── */

/* EL AVISO FIJO (chore 90.257 · contrato §3, `.alert` de Hugin): el canto en el
   color GRÁFICO del estado y el fondo en su tinte del 9 %; la letra, en el token de
   TEXTO (S-7). F-8 sigue: el 409 se enseña aquí con la frase literal del servidor.
   Rojo = algo FALLÓ · ámbar = no puedes o no sigue · azul = para que lo sepas; el
   color no va solo, el texto del aviso es la etiqueta.

   Medido (chore 90.257), la letra da como poco 5.69:1 en claro y 5.82:1 en oscuro.
   El CANTO baja de 3:1 en las dos celdas que ya anotó la chore 90.250 —el verde
   del claro (2.70-3.01:1) y el azul del oscuro (2.60-3.41:1)—, y por el mismo
   motivo no se corrigen: el canto nunca es el único portador; lo es la frase. */
.bf-msg {
  border-radius: var(--bf-radius-modal);
  padding: 0.75rem 1rem;
  font-size: var(--bf-table-row-font-size);
  line-height: 1.45;
  display: flex;
  gap: 10px;
  align-items: flex-start;
  border: 1px solid;
}

.bf-msg.err {
  color: var(--bf-danger-text);
  border-color: var(--bf-danger);
  background-color: color-mix(in srgb, var(--bf-danger) 9%, transparent);
}

.bf-msg.ok {
  color: var(--bf-success-text);
  border-color: var(--bf-success);
  background-color: color-mix(in srgb, var(--bf-success) 9%, transparent);
}

.bf-msg.warn {
  color: var(--bf-warning-text);
  border-color: var(--bf-warning);
  background-color: color-mix(in srgb, var(--bf-warning) 9%, transparent);
}

.bf-msg.info {
  color: var(--bf-info-text);
  /* Neutro desde la 90.648: el filete de la tinta apagada entera pesaba como un error; el de la tarjeta basta. */
  border-color: var(--bf-border);
  background-color: color-mix(in srgb, var(--bf-info) 9%, transparent);
}

/* Un mensaje puede llevar VARIOS motivos a la vez —el asistente de arranque valida
   el alta entera de un golpe (chore 90.067)—. La lista se apila dentro del recuadro
   y pierde el punto: el color ya dice que son fallos, y el punto sólo mete sangría. */
.bf-msg ul {
  display: flex;
  flex-direction: column;
  gap: 4px;
  list-style: none;
  margin: 0;
  padding-inline-start: 0;
}

/* EL VACÍO (chore 90.257 · contrato §3, `emptyState` de Hugin): MARCA, TÍTULO, NOTA
   y SALIDA. Era una línea gris centrada, y una frase suelta no dice qué situación
   es: no es lo mismo una lista recién estrenada que un filtro que no deja nada.
   El marcado no cambia —`<b>` es el título, `<span>` la nota y lo que venga
   detrás, la salida (el tag helper <bf-empty> ya lo emitía así)—; la marca la
   pone la hoja, así que la llevan también los vacíos que se pintan desde C#.

   Es una rejilla de una columna, y el truco está en la marca: sus dos piezas
   (::before, el disco; ::after, el dibujo) se clavan en la celda 1/1 y los hijos,
   que se colocan solos, saltan a las filas siguientes porque esa celda ya está
   ocupada. */
.bf-empty {
  display: grid;
  justify-items: center;
  align-content: center;
  gap: 0.25rem;
  padding: 3rem 1.5rem;
  text-align: center;
  font-size: var(--bf-text-body);
  color: var(--bf-muted);
}

.bf-empty::before,
.bf-empty::after {
  content: "";
  grid-area: 1 / 1;
  place-self: center;
  margin-bottom: 0.5rem;
}

/* El disco, con UN aro alrededor y no dos: dos concéntricos dibujan una diana, y
   una diana pide mirada, que es lo contrario de lo que necesita la pieza que dice
   «aquí no hay nada». El aro es el contorno desplazado, que sigue al radio. */
.bf-empty::before {
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-accent) 13%, transparent);
  outline: 1px solid color-mix(in srgb, var(--bf-accent) 16%, transparent);
  outline-offset: 0.7rem;
}

.bf-empty::after {
  width: 1.45rem;
  height: 1.45rem;
  background-color: var(--bf-accent);
  -webkit-mask: var(--bf-empty-mark) center / contain no-repeat;
  mask: var(--bf-empty-mark) center / contain no-repeat;
}

.bf-empty[data-mark="search"]::after {
  -webkit-mask-image: var(--bf-empty-mark-search);
  mask-image: var(--bf-empty-mark-search);
}

.bf-empty > b {
  font-size: var(--bf-text-lead);
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-empty > span {
  max-width: 34rem;
  font-size: var(--bf-text-ui);
  line-height: 1.5;
}

/* La salida: un enlace o un botón debajo de la nota, separado de ella. */
.bf-empty > .bf-btn {
  margin-top: 1rem;
}

/* EL VACÍO COMPACTO (chore 90.682): el mismo vacío para los huecos pequeños —una
   tarjeta lateral, un panel del editor, un bloque de la panorámica—, con la marca y
   el relleno a escala. Todo vacío sale centrado a lo ancho y a lo alto de su
   contenedor; un párrafo suelto a la izquierda se leía como un texto olvidado. */
.bf-empty.sm {
  gap: 0.2rem;
  padding: 1.5rem 1rem;
  font-size: var(--bf-text-ui);
}

.bf-empty.sm::before {
  width: 2.5rem;
  height: 2.5rem;
  outline-offset: 0.5rem;
}

.bf-empty.sm::after {
  width: 1.1rem;
  height: 1.1rem;
}

.bf-empty.sm > b {
  font-size: var(--bf-text-body);
}

.bf-empty.sm > span {
  font-size: var(--bf-text-label);
}

/* EL ESQUELETO (chore 90.259 · contrato §3, `.skeleton` de Hugin): lo que ocupa el
   sitio de un contenido que está EN CAMINO. Una región que salta de vacía a llena se
   lee como un parpadeo de avería; con el esqueleto, el hueco ya tiene la forma de lo
   que va a llegar y lo que cambia es sólo el relleno.

   El barrido es un BUCLE a propósito, y es uno de los dos que admite el sistema —el
   otro es el aro del botón ocupado—: «esperando» es lo único que se dice con
   movimiento continuo, y se va solo cuando llegan los datos. Tinte y barrido salen de
   la tinta apagada, así que siguen al tema sin repetirse.

   Quien lo pinte, con dos reglas (las de Hugin): SÓLO en la primera carga —si ya hay
   contenido se deja el de antes; repintar huesos con cada tecla es un parpadeo— y
   fuera del árbol de accesibilidad (`aria-hidden`): quien dice que se espera es el
   `aria-busy` de la región, no unas cajas vacías. Y tiene que retirarlo en los DOS
   caminos, con el dato o con el fallo: un esqueleto que sigue brillando tras un
   error dice «cargando» sobre algo que ya no va a llegar.

   Lo gastan las dos esperas de verdad (rediseño general): la VISTA que llega por la
   capa de navegación sin recargas (`.bf-view-skel`, abajo) y la ficha del cajón de
   auditoría (`.bf-detail-skel`). Lo demás llega pintado del servidor. */
.bf-skel {
  /* Barrido POR PIEZA (chore 90.388, opción A de la maqueta): cada bloque lleva su
     propio brillo que lo cruza, sobre el tinte de la tinta del tema. */
  --bf-sk: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  --bf-sk-hi: color-mix(in srgb, var(--bf-relief-light) 75%, transparent);
  display: block;
  height: 12px;
  border-radius: 6px;
  background: linear-gradient(90deg, var(--bf-sk) 0%, var(--bf-sk-hi) 50%, var(--bf-sk) 100%) var(--bf-sk);
  background-size: 200% 100%;
  animation: bf-skel-shine var(--bf-dur-sweep) ease-in-out infinite;
}

:root[data-theme="dark"] .bf-skel {
  --bf-sk: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  --bf-sk-hi: color-mix(in srgb, var(--bf-surface-fg) 12%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-skel {
    --bf-sk: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
    --bf-sk-hi: color-mix(in srgb, var(--bf-surface-fg) 12%, transparent);
  }
}

/* La barra que sustituye a UN renglón concreto: sin alto propio, lo toma de la línea
   en la que va —lleva un espacio duro dentro—, así mide exactamente lo que va a medir
   el texto que llegue. Un alto inventado hace saltar la región al cargar. */
.bf-skel.fill {
  display: inline-block;
  height: auto;
  vertical-align: middle;
}

/* El hueco de una celda con mandos: lo que fija el alto de una fila no es su texto,
   es el botón o la pastilla que lleva al lado. */
.bf-skel-cell {
  display: flex;
  align-items: center;
  min-height: 1.5rem;
}

/* LA VISTA QUE LLEGA (rediseño general; forma de la chore 90.388): si la siguiente
   tarda más de 150 ms, la capa de navegación (ts/ui/trip.ts) pone en el sitio de la de
   antes el título, su entradilla y un panel con pocas filas —círculo, dos líneas y una
   pastilla—, hasta que aterriza. La de antes se esconde entera: ya no es la que se va
   a ver. Entra fundiéndose, para que una espera corta no se lea como un golpe. */
[data-nav-region][data-nav-wait] > :not(.bf-view-skel) {
  display: none !important;
}

.bf-view-skel {
  display: grid;
  gap: 12px;
  padding-top: 0.35rem;
  animation: bf-view-skel-in var(--bf-dur-base) var(--bf-ease-out) both;
}

.bf-view-skel > .ttl { width: 45%; height: 20px; }
.bf-view-skel > .sub { width: 60%; }

.bf-view-skel-card {
  display: grid;
  gap: 12px;
  width: min(520px, 100%);
  padding: 16px;
  border-radius: 16px;
  background: var(--bf-card-bg);
}

.bf-view-skel-row {
  display: grid;
  grid-template-columns: 32px 1fr 60px;
  gap: 12px;
  align-items: center;
}

.bf-view-skel-row > .dot { width: 32px; height: 32px; border-radius: 50%; }
.bf-view-skel-row > .lines { display: grid; gap: 6px; }
.bf-view-skel-row > .pill { height: 32px; border-radius: 9px; }
.bf-skel.w20 { width: 20%; }
.bf-skel.w30 { width: 30%; }
.bf-skel.w45 { width: 45%; }
.bf-skel.w60 { width: 60%; }
.bf-skel.w80 { width: 80%; }

/* LA FICHA DEL CAJÓN DE AUDITORÍA: su `bf-dl` de verdad con barras dentro (el alto lo
   pone la tipografía de la ficha), el rótulo de la comparación y los renglones
   «antes / después» en dos columnas, como el `.bf-diff` que va a llegar. */
.bf-detail-skel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 0.25rem 0;
}

.bf-detail-skel .bf-skel.fill.w-s { width: 5.5rem; }
.bf-detail-skel .bf-skel.fill.w-m { width: 8rem; }
.bf-detail-skel .bf-skel.fill.w-l { width: 11rem; }

.bf-detail-skel-label {
  width: 8rem;
  margin-top: 0.5rem;
}

.bf-detail-skel-diff {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px 1rem;
  padding: 16px;
  border-radius: 16px;
  background: var(--bf-card-bg);
}

.bf-detail-skel-diff > .bf-skel:nth-child(odd) { width: 80%; }
.bf-detail-skel-diff > .bf-skel:nth-child(even) { width: 60%; }

@keyframes bf-view-skel-in {
  from {
    opacity: 0;
  }
}

/* El brillo de cada pieza (chore 90.388). `bf-skel-sweep` se queda para la barra de
   navegación, que lo sigue gastando. */
@keyframes bf-skel-shine {
  from {
    background-position: 150% 0;
  }
  to {
    background-position: -50% 0;
  }
}

@keyframes bf-skel-sweep {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -100% 0;
  }
}

/* PARADO, y no a un milisegundo. Las duraciones de tokens.css caen a 1 ms con el
   movimiento reducido, pero un bucle infinito a un milisegundo no se para: PARPADEA.
   Por eso --bf-dur-sweep no está en ese apagado y la pieza se para aquí, por las dos
   vías —la del sistema y la del atributo reduce="motion" (contrato 00.004 §5.1)—.
   Se queda el tinte plano, que sigue diciendo que ahí va a haber algo. */
@media (prefers-reduced-motion: reduce) {
  .bf-skel {
    animation: none;
    background-image: none;
  }
}

:root[data-reduce~="motion"] .bf-skel {
  animation: none;
  background-image: none;
}

/* ═══ 4 · Chasis de acceso (páginas sin shell) ═══════════════════════════════ */

/* LA PUERTA (rediseño general de la rama Dragsa-General-Design). Las pantallas sin
   sesión —login, segundo factor, cambio forzado, recuperación, cierre de sesión y
   arranque— llevan el vestido del login de LATANUR: una tarjeta de cristal centrada
   sobre un cielo (antes la ola de luz), con la marca encima. Así las dos puertas de la familia se ven
   iguales. Lo que NO cambia es la estructura de cada pantalla —los campos, la
   federación, la recuperación, el estado del servicio—: cambia el vestido. Antes era
   el doble panel de la maqueta (chore 90.062), con la marca sobre pizarra.

   Los valores son los de Latanur (`src/front/css/auth/*.css`) dichos con los tokens
   de Bifrost, que ya valen lo mismo porque los dos siguen el sistema de Hugin (T20).
   Las tintas del cielo, que sólo existen aquí, son los --bf-door-* de tokens.css.
   Mobile-first (MF-2): la base es el teléfono y el corte de 620 (MF-3) devuelve el
   aire y el cielo a pantalla completa. */
.bf-door {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--bf-door-halo), var(--bf-door-stage);
  color: var(--bf-surface-fg);
  /* EL CRISTAL DE LA TARJETA, menos cerrado que el de lo que flota sobre datos: aquí
     debajo no hay contenido que borrar sino una ola que se quiere ver pasar. Y cinco
     píxeles, no más: por encima el navegador desenfoca una miniatura y el filo de la
     ola, al moverse, parpadea por dentro de la tarjeta. */
  --bf-door-card-filter: blur(5px) saturate(1.3);
  /* EL CRISTAL DEL PANEL DEL FORMULARIO DEL ACCESO, y el de la caja, que es el mismo.
     A 12 px, y con el velo al 55 %, la ola se deshacía detrás en una mancha lechosa que
     no casaba con su filo nítido de fuera; a 8 px y con el velo más abierto se lee como
     la MISMA ola pasando por detrás del vidrio. */
  --bf-door-panel-filter: blur(8px) saturate(1.35);
  /* El canto: una línea de luz, no una moldura. Medio píxel de ceja y el borde a
     poco más de la mitad de la tinta del canto de lo elevado. */
  --bf-door-card-bevel: inset 1px 1px 0 -0.5px var(--bf-bevel-hi), inset -1px -1px 0 -0.5px var(--bf-bevel-lo);
  --bf-door-card-edge: color-mix(in srgb, var(--bf-elevated-edge) 45%, transparent);
  /* El cristal PLANO, punto de partida de lo que entra formándose. En variable y no
     escrito en el fotograma: con el mismo texto en la estándar y en la de WebKit, un
     minificador da una por repetida y borra la estándar. */
  --bf-door-filter-flat: blur(0px) saturate(1);
  /* LA CURVA DE LAS ENTRADAS: arranca con decisión y FRENA muy despacio, sin rebote.
     Con la de Latanur (0.22, 0.61, 0.36, 1) y 0,7 s la marca y la tarjeta llegaban
     de golpe; ésta, con algo más de tiempo y medio rem de recorrido, se posa. Los
     segmentados de arriba conservan su rebote de Latanur. */
  --bf-door-ease: cubic-bezier(0.16, 1, 0.3, 1);
  /* EL MARGEN DE LA PUERTA, UNO PARA LOS DOS LADOS: el logo y la promesa empiezan a
     la misma distancia del borde izquierdo a la que acaban los campos de la tarjeta en
     el derecho. Escrito una vez, no pueden descuadrarse. */
  --bf-door-gutter: 1.5rem;
}

@media (min-width: 900px) {
  .bf-door {
    --bf-door-gutter: 2.5rem;
  }
}

/* ── El cielo: la planta y el dato en 3D (chores 90.500 y 90.583) ─────────────
   Detrás de todo y sin atrapar el puntero. La viñeta devuelve las esquinas a negro
   para que la tarjeta siga siendo lo más claro de la pantalla (en claro, apenas).
   Dos capas: la LÁMINA del marcado, quieta, que se ve de entrada y se queda sin
   JavaScript o sin WebGL; y el LIENZO 3D de door-sky.ts, que al arrancar marca el
   cielo `is-live` y se funde encima mientras la lámina se va. Sin rótulos: la
   escena no nombra nada. Mobile-first: en el teléfono la escena va detrás de la
   tarjeta, un punto atenuada; el corte de 900 la sube a su tinta plena. */
.bf-door-sky {
  position: fixed;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  --bf-door-orbit-dim: 0.9;
}

@media (min-width: 900px) {
  .bf-door-sky {
    --bf-door-orbit-dim: 1;
  }
}

.bf-door-sky::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--bf-door-vignette);
}

/* SIN VERSIÓN BÁSICA AL CARGAR (chore 90.586): la lámina NO se ve mientras llega el
   3D —se veía un instante una escena plana que luego se cambiaba por otra—. Hasta
   que la escena arranca el cielo está vacío, y la escena se construye sola
   (door-sky-paint.ts). La lámina sólo aparece como RESPALDO: sin JavaScript
   (`scripting: none`) o cuando door.ts marca el cielo `is-fallback` (sin WebGL, el
   módulo falla o se pierde el contexto). */
.bf-door-plate,
.bf-door-orbit {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
}

.bf-door-plate {
  transition: opacity 1.2s var(--bf-door-ease);
}

.bf-door-sky.is-live .bf-door-orbit,
.bf-door-sky.is-fallback .bf-door-plate {
  opacity: calc(var(--bf-door-orbit-opacity) * var(--bf-door-orbit-dim));
}

@media (scripting: none) {
  .bf-door-plate {
    opacity: calc(var(--bf-door-orbit-opacity) * var(--bf-door-orbit-dim));
  }
}

/* La lámina, tinta por clase: un color literal en el marcado rompería el tema dual. */
.bf-door-plate ellipse,
.bf-door-plate line {
  fill: none;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}

.bf-door-plate .floor ellipse { stroke: var(--bf-surface-fg); stroke-opacity: 0.12; }
.bf-door-plate .asset path {
  fill: none;
  stroke: var(--bf-surface-fg);
  stroke-opacity: 0.42;
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}
.bf-door-plate .asset circle { fill: var(--bf-door-flame); }
.bf-door-plate .edge line { stroke: var(--bf-door-flame); stroke-opacity: 0.22; }
.bf-door-plate .edge circle { fill: var(--bf-door-flame); }
.bf-door-plate .orbit ellipse { stroke: var(--bf-surface-fg); stroke-opacity: 0.14; }
.bf-door-plate .sat circle { fill: color-mix(in srgb, var(--bf-surface-fg) 65%, var(--bf-door-core)); }
.bf-door-plate .core circle { fill: var(--bf-door-core); }
.bf-door-plate .glow-in { stop-color: var(--bf-door-flame); stop-opacity: 0.45; }
.bf-door-plate .glow-mid { stop-color: var(--bf-door-ember); stop-opacity: 0.14; }
.bf-door-plate .glow-out { stop-color: var(--bf-door-ember); stop-opacity: 0; }
.bf-door-plate .ring ellipse { stroke-width: 1.5; }
.bf-door-plate .ring .band-a { stroke: var(--bf-door-flame); stroke-opacity: 0.55; }
.bf-door-plate .ring .band-b { stroke: var(--bf-door-ember); stroke-opacity: 0.4; }

/* ── La ola (chore 90.584) ──────────────────────────────────────────────────────
   El fondo de la puerta salvo en el login, que lleva la escena: la ola de luz de
   Latanur, dentro del mismo cielo (`.bf-door-sky`). */
/* En el teléfono la ola va en una banda baja: a pantalla completa y recortada por
   el alto se quedaba en un trazo casi recto. */
.bf-door-wave {
  position: absolute;
  inset: auto 0 16%;
  width: 100%;
  height: 54%;
  /* La ola entera, un punto por debajo de su tinta plena: es fondo, no protagonista. */
  opacity: var(--bf-door-wave-opacity, 1);
}

@media (min-width: 620px) {
  .bf-door-wave {
    inset: 0;
    height: 100%;
  }
}

/* Las paradas se pintan desde aquí y no con `stop-color` en el marcado: un color
   literal en el HTML rompería el tema dual. La misma curva vale para los dos temas
   cambiando cuatro tintas. */
.bf-door-wave .ink-0 { stop-color: var(--bf-door-ember); stop-opacity: 0; }
.bf-door-wave .ink-1 { stop-color: var(--bf-door-ember); }
.bf-door-wave .ink-2 { stop-color: var(--bf-door-flame); }
.bf-door-wave .ink-3 { stop-color: var(--bf-door-core); }
.bf-door-wave .ink-4 { stop-color: var(--bf-door-flame); }
.bf-door-wave .ink-5 { stop-color: var(--bf-door-ember); stop-opacity: 0; }
.bf-door-wave .bloom-in { stop-color: var(--bf-door-flame); stop-opacity: var(--bf-door-bloom-inner); }
.bf-door-wave .bloom-mid { stop-color: var(--bf-door-ember); stop-opacity: var(--bf-door-bloom-middle); }
.bf-door-wave .bloom-out { stop-color: var(--bf-door-ember); stop-opacity: 0; }

/* LA OLA ES UNA CINTA, NO UNA LÍNEA: trazos concéntricos sobre la misma curva, del
   más ancho y apagado al más fino y encendido. El ojo los suma y lee una masa de luz
   con el canto blando y el núcleo caliente. Los grosores son geometría y no cambian
   con el tema; lo que cambia es cuánta tinta llevan los halos, con un solo número. */
.bf-door-wave .halos { opacity: var(--bf-door-halo-strength); }
.bf-door-wave .halo-1 { stroke-width: 84; opacity: 0.0156; }
.bf-door-wave .halo-2 { stroke-width: 73.2; opacity: 0.0158; }
.bf-door-wave .halo-3 { stroke-width: 66.1; opacity: 0.0161; }
.bf-door-wave .halo-4 { stroke-width: 60.6; opacity: 0.0163; }
.bf-door-wave .halo-5 { stroke-width: 55.9; opacity: 0.0166; }
.bf-door-wave .halo-6 { stroke-width: 51.8; opacity: 0.0169; }
.bf-door-wave .halo-7 { stroke-width: 48; opacity: 0.0172; }
.bf-door-wave .halo-8 { stroke-width: 44.5; opacity: 0.0175; }
.bf-door-wave .halo-9 { stroke-width: 41.1; opacity: 0.0178; }
.bf-door-wave .halo-10 { stroke-width: 37.9; opacity: 0.0181; }
.bf-door-wave .halo-11 { stroke-width: 34.7; opacity: 0.0184; }
.bf-door-wave .halo-12 { stroke-width: 31.5; opacity: 0.0188; }
.bf-door-wave .halo-13 { stroke-width: 28.2; opacity: 0.0191; }
.bf-door-wave .halo-14 { stroke-width: 24.8; opacity: 0.0195; }
.bf-door-wave .halo-15 { stroke-width: 21.1; opacity: 0.0199; }
.bf-door-wave .halo-16 { stroke-width: 17; opacity: 0.0203; }
.bf-door-wave .halo-17 { stroke-width: 11.8; opacity: 0.0207; }
.bf-door-wave .glow-1 { stroke-width: 26; opacity: 0.0286; }
.bf-door-wave .glow-2 { stroke-width: 20.9; opacity: 0.0294; }
.bf-door-wave .glow-3 { stroke-width: 17.2; opacity: 0.0303; }
.bf-door-wave .glow-4 { stroke-width: 13.9; opacity: 0.0312; }
.bf-door-wave .glow-5 { stroke-width: 10.8; opacity: 0.0323; }
.bf-door-wave .glow-6 { stroke-width: 7.3; opacity: 0.0333; }

.bf-door-wave .core {
  stroke-width: var(--bf-door-core-width);
  opacity: var(--bf-door-core-opacity);
}

/* Las dos hebras de acompañamiento, más apagadas que la principal y una más que la
   otra: es lo que las pone a distintas distancias. */
.bf-door-wave .strand.over { opacity: var(--bf-door-strand-over); }
.bf-door-wave .strand.under { opacity: var(--bf-door-strand-under); }
.bf-door-wave .strand.over .core { stroke-width: calc(var(--bf-door-core-width) * 0.62); }
.bf-door-wave .strand.under .core { stroke-width: calc(var(--bf-door-core-width) * 0.74); }
.bf-door-wave .grain { opacity: var(--bf-door-grain); }

/* LA OLA SE ASIENTA una vez al entrar: tres segundos, lineal, y se queda quieta. Las
   tres hebras llevan el mismo trazado y la misma animación, así que se mueven a una
   por construcción. */
.bf-door-wave .ribbon path,
.bf-door-wave .bloom {
  animation-duration: 3s;
  animation-timing-function: linear;
  animation-fill-mode: both;
  animation-delay: 0.12s;
}

/* LA OLA SE COLOCA RESPECTO A LA PROMESA: door.ts mide la tercera píldora y escribe
   aquí cuánto subir o bajar la ola (en unidades del dibujo) para que la cruce en
   parte. Se mueven la cinta y sus manchas; el grano, que cubre el dibujo entero, no. */
.bf-door-wave .ribbon,
.bf-door-wave .bloom {
  transform: translateY(var(--bf-door-wave-shift, 0px));
}

.bf-door-wave .ribbon path { animation-name: bf-door-swell; }
.bf-door-wave .bloom-a { animation-name: bf-door-bloom-a; }
.bf-door-wave .bloom-b { animation-name: bf-door-bloom-b; }

@keyframes bf-door-swell {
  0% { d: path("M -160 756 C 250 671.1, 430 522, 780 549.1 S 1190 653, 1600 522"); }
  5% { d: path("M -160 771.1 C 250 683.4, 430 528, 780 540.5 S 1190 636.7, 1600 515.8"); }
  10% { d: path("M -160 782 C 250 697.6, 430 538.3, 780 537.7 S 1190 622.3, 1600 505.5"); }
  15% { d: path("M -160 787.9 C 250 711.5, 430 550.8, 780 540.3 S 1190 611.3, 1600 492.9"); }
  20% { d: path("M -160 788.7 C 250 723.2, 430 563.6, 780 547.1 S 1190 604.8, 1600 480.1"); }
  25% { d: path("M -160 785.1 C 250 731.4, 430 574.7, 780 556.5 S 1190 603, 1600 469"); }
  30% { d: path("M -160 778.3 C 250 735.6, 430 582.9, 780 566.8 S 1190 605.3, 1600 460.9"); }
  35% { d: path("M -160 769.9 C 250 735.9, 430 587.6, 780 576.3 S 1190 610.7, 1600 456.3"); }
  40% { d: path("M -160 761.3 C 250 732.9, 430 588.7, 780 583.8 S 1190 617.8, 1600 455.3"); }
  45% { d: path("M -160 753.9 C 250 727.7, 430 586.7, 780 588.6 S 1190 625.3, 1600 457.4"); }
  50% { d: path("M -160 748.6 C 250 721.5, 430 582.6, 780 590.5 S 1190 632, 1600 461.5"); }
  55% { d: path("M -160 745.7 C 250 715.6, 430 577.4, 780 589.8 S 1190 636.9, 1600 466.7"); }
  60% { d: path("M -160 745.2 C 250 710.9, 430 572.4, 780 587.3 S 1190 639.7, 1600 471.7"); }
  65% { d: path("M -160 746.5 C 250 707.8, 430 568.2, 780 583.8 S 1190 640.4, 1600 475.9"); }
  70% { d: path("M -160 748.9 C 250 706.5, 430 565.5, 780 580.3 S 1190 639.5, 1600 478.6"); }
  75% { d: path("M -160 751.6 C 250 706.7, 430 564.3, 780 577.4 S 1190 637.6, 1600 479.7"); }
  80% { d: path("M -160 754 C 250 707.9, 430 564.4, 780 575.5 S 1190 635.4, 1600 479.6"); }
  85% { d: path("M -160 755.6 C 250 709.5, 430 565.4, 780 574.8 S 1190 633.5, 1600 478.6"); }
  90% { d: path("M -160 756.3 C 250 710.9, 430 566.6, 780 574.9 S 1190 632.4, 1600 477.4"); }
  95% { d: path("M -160 756.3 C 250 711.7, 430 567.6, 780 575.5 S 1190 631.9, 1600 476.4"); }
  100% { d: path("M -160 756 C 250 712, 430 568, 780 576 S 1190 632, 1600 476"); }
}

@keyframes bf-door-bloom-a {
  0% { cy: 682.6px; }
  10% { cy: 691.4px; }
  20% { cy: 701.6px; }
  30% { cy: 707.9px; }
  40% { cy: 708.4px; }
  50% { cy: 704.8px; }
  60% { cy: 700.6px; }
  70% { cy: 698.3px; }
  80% { cy: 698.4px; }
  90% { cy: 699.5px; }
  100% { cy: 700px; }
}

@keyframes bf-door-bloom-b {
  0% { cy: 559.7px; }
  10% { cy: 548.4px; }
  20% { cy: 543.8px; }
  30% { cy: 546.3px; }
  40% { cy: 552.3px; }
  50% { cy: 557.7px; }
  60% { cy: 559.8px; }
  70% { cy: 558.9px; }
  80% { cy: 557px; }
  90% { cy: 556px; }
  100% { cy: 556px; }
}

/* Con más contraste, sin cielo: la preferencia existe para que no haya nada detrás
   del texto, y una tarjeta de cristal con una constelación por debajo es eso. */
@media (prefers-contrast: more) {
  .bf-door-sky {
    display: none;
  }

  .bf-door {
    background: var(--bf-door-stage);
  }
}

/* ── Lo que se pone encima ─────────────────────────────────────────────────────
   Una columna centrada: la marca, la tarjeta y, en el acceso, el estado debajo. Los
   dos chasis (acceso y caja centrada) son la misma puerta. */
.bf-centered {
  position: relative;
  z-index: 1;
  min-height: 100vh;
  min-height: 100dvh;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 1.5rem;
  padding: 3.5rem 1rem 4rem; /* arriba, el hueco de la barra de preferencias */
}

/* EL ACCESO CONSERVA SU ESTRUCTURA: el doble panel de la chore 90.062. A la izquierda
   la marca y su promesa —QUÉ es esto—; a la derecha el formulario en la tarjeta de
   cristal —qué hay que HACER— y, a su pie, DÓNDE se está entrando (el servicio, la
   instalación con su versión y la organización). Lo que cambia es el vestido: ya no
   hay pizarra y por detrás de las dos mitades pasa la ola.
   Mobile-first (MF-2): en el teléfono una sola columna, con la marca arriba y la
   tarjeta; la promesa es contexto y espera al corte de 900 (MF-3). */
.bf-auth {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto 1fr;
  min-height: 100vh;
  min-height: 100dvh;
}

.bf-auth .art {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 3.5rem var(--bf-door-gutter) 0; /* arriba, el hueco de la barra de preferencias */
}

.bf-auth .art > div:not(.bf-mark-lg) {
  display: none;
}

/* EL FORMULARIO ES LA TARJETA, ALARGADA: una columna a la derecha, de arriba abajo,
   como el panel del formulario del doble panel de siempre, pero de CRISTAL —el velo
   de la superficie, abierto; el desenfoque, fuerte; y su canto de luz—, así que la
   ola se ve pasar también por detrás del formulario. El formulario va centrado en
   ella y el pie, abajo. En el teléfono es el bloque de debajo de la marca. */
.bf-auth .form {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 2rem var(--bf-door-gutter) 1.75rem;
  background-color: color-mix(in srgb, var(--bf-door-glass-tint) var(--bf-door-panel-veil), transparent);
  -webkit-backdrop-filter: var(--bf-door-panel-filter);
  backdrop-filter: var(--bf-door-panel-filter);
  border-top: 1px solid var(--bf-door-card-edge);
  box-shadow: var(--bf-door-card-bevel), var(--bf-door-panel-drop);
  animation: bf-door-glass-in 1.2s var(--bf-door-ease) 0.08s both;
}

/* Lo que se LEE dentro no crece con la mitad (chore 90.094): una columna de 32,5 rem
   —29,5 en la variante estrecha— centrada en la tarjeta. Con media pantalla de ancho
   en un monitor grande, el botón de «Entrar» salía de ochocientos píxeles. */
.bf-auth .card {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  width: 100%;
  max-width: 32.5rem;
  flex: 1 0 auto;
}

/* LA TARJETA LLEGA HASTA EL PIE (chore 90.619) y lo que se rellena va centrado en
   ella: el primer bloque se separa de arriba lo mismo que el último de abajo. Antes la
   tarjeta entera iba centrada en la columna, y el derecho de autor —que es lo último
   de ella— seguía al panel visible: al pasar a recuperar la contraseña, que mide
   menos, se quedaba a media altura. Ahora es el PIE de la tarjeta, abajo del todo y
   quieto (su regla le da el margen automático que lo empuja), y no cambia de sitio al
   cambiar de panel. El primero que CUENTA es el primero que ocupa sitio: el aviso
   para el lector de pantalla del login (`.bf-sr`) va delante y no ocupa nada. */
.bf-auth .card > :first-child:not(.bf-sr),
.bf-auth .card > .bf-sr:first-child + * {
  margin-top: auto;
}

.bf-auth .card > :last-child:not(.copyright) {
  margin-bottom: auto;
}

.bf-auth .form.narrow .card {
  max-width: 29.5rem;
}

/* ── LA PROMESA: lo que la mitad izquierda cuenta ──────────────────────────────
   Es lo único que se lee antes de rellenar nada, así que va GRANDE y con carácter:
   una raya corta de acento que la anuncia, la frase a tamaño de cartel —crece con
   la ventana, de 2,25 a 3,5 rem— y la organización resaltada en el degradado de la
   ola, con su resplandor. Debajo, la entradilla en cuerpo de lectura y lo que se
   gana entrando, cada cosa con su marca. Todo entra escalonado, de arriba abajo. */
.bf-auth .art .pitch {
  position: relative;
  max-width: 44rem;
}

/* Un resplandor cálido y muy abierto detrás de la frase: la despega del escenario sin
   ser una caja. Va detrás de todo lo del panel, pero delante de la ola. */
.bf-auth .art .pitch::before {
  content: "";
  position: absolute;
  inset: -40% -25% -40% -30%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--bf-accent) 6%, transparent), transparent);
}

/* El antetítulo, con la raya de acento que anuncia la frase. */
.bf-auth .art .pitch-eyebrow {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--bf-muted);
  animation: bf-door-rise 1.1s var(--bf-door-ease) 0.05s both;
}

.bf-auth .art .pitch-eyebrow::before {
  content: "";
  flex: none;
  width: 2.5rem;
  height: 1px;
  background: linear-gradient(90deg, color-mix(in srgb, var(--bf-surface-fg) 45%, transparent), transparent);
}

.bf-auth .art h2 {
  max-width: 20ch;
  font-size: clamp(2rem, 1rem + 1.75vw, 3rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1.04;
  text-wrap: balance;
  animation: bf-door-rise 1.1s var(--bf-door-ease) 0.1s both;
}

.bf-auth .art h2 .hl {
  background: linear-gradient(100deg, var(--bf-door-mark), var(--bf-door-hl-end));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 0.375rem color-mix(in srgb, var(--bf-accent) 16%, transparent));
}

.bf-auth .art p {
  max-width: 44ch;
  margin-top: 1.25rem;
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--bf-muted);
  animation: bf-door-rise 1.1s var(--bf-door-ease) 0.2s both;
}

.bf-auth .art .perks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  margin: 2rem 0 0;
  padding: 0;
  list-style: none;
}

/* Cada cosa, una PÍLDORA de cristal: el mismo material que la tarjeta, en pequeño. */
.bf-auth .art .perks li {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.3125rem 0.875rem 0.3125rem 0.3125rem;
  border: 1px solid var(--bf-door-card-edge);
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) var(--bf-door-pill-veil), transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  box-shadow: var(--bf-door-card-bevel);
  font-size: var(--bf-text-ui);
  font-weight: 500;
  color: var(--bf-surface-fg);
  animation: bf-door-rise 1.1s var(--bf-door-ease) both;
}

.bf-auth .art .perks li:nth-child(1) { animation-delay: 0.32s; }
.bf-auth .art .perks li:nth-child(2) { animation-delay: 0.4s; }
.bf-auth .art .perks li:nth-child(3) { animation-delay: 0.48s; }

/* La marca de cada cosa: un círculo neutro —el acento se queda en la organización—. */
.bf-auth .art .perks i {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
  color: var(--bf-surface-fg);
}

.bf-auth .art .perks i > svg {
  width: 0.8rem;
  height: 0.8rem;
  stroke-width: 2.4;
}

@media (min-width: 900px) {
  /* La tarjeta es una COLUMNA, no media pantalla: lo que se rellena son dos campos,
     y la marca y su promesa se quedan con el resto del ancho. */
  .bf-auth {
    grid-template-columns: minmax(0, 1fr) clamp(25rem, 34vw, 31rem);
    grid-template-rows: none;
  }

  /* La PROMESA, centrada en vertical en la vista entera; la marca va fija arriba,
     fuera del reparto, para que su alto no empuje la frase hacia abajo. */
  .bf-auth .art {
    position: relative;
    justify-content: center;
    padding: 2.875rem var(--bf-door-gutter);
  }

  .bf-auth .art > .bf-mark-lg {
    position: absolute;
    top: 1.5rem;
    left: var(--bf-door-gutter);
  }

  .bf-auth .art > div:not(.bf-mark-lg) {
    display: block;
  }

  /* Su canto va a la izquierda, que es donde se encuentra con el panel de la marca. */
  .bf-auth .form {
    padding: 2.875rem var(--bf-door-gutter) 1.75rem;
    border-top: 0;
    border-left: 1px solid var(--bf-door-card-edge);
  }
}

/* Detrás del login pasa la planta en 3D (chores 90.583 y 90.588) y se ve a través del
   cristal. Se probó a 3 px para que sus aristas se leyeran, pero ahora la escena pasa
   de verdad por debajo del formulario y lo que se pide es CRISTAL ESMERILADO: a 12 px
   la planta queda en formas y luces, y el texto no compite con ella. */
.bf-door-sky:has(> .bf-door-plate) ~ .bf-auth .form {
  --bf-door-panel-filter: blur(12px) saturate(1.35);
}

/* LA CAJA, de cristal de verdad, y el MISMO que la tarjeta del acceso —su velo y su
   desenfoque—: recuperar la contraseña es la misma puerta que entrar, y dos cristales
   distintos se leían como dos productos. Se mezcla desde la SUPERFICIE —lo que una
   tarjeta es en el resto de la aplicación— y no desde el lienzo.
   25 rem por token y no clavado: la caja ancha del arranque declara la suya. */
.bf-centered .box {
  width: min(var(--bf-door-card-w, 25rem), 100%);
  padding: 1.5rem;
  border-radius: var(--bf-radius-lg);
  background-color: color-mix(in srgb, var(--bf-door-glass-tint) var(--bf-door-panel-veil), transparent);
  -webkit-backdrop-filter: var(--bf-door-panel-filter);
  backdrop-filter: var(--bf-door-panel-filter);
  border: 1px solid var(--bf-door-card-edge);
  box-shadow: var(--bf-door-card-bevel), var(--bf-door-card-shadow);
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  animation: bf-door-glass-in 1.2s var(--bf-door-ease) 0.08s both;
}

/* Densidad calmada: la tarjeta es LO ÚNICO que hay, y con relleno de tarjeta de
   tablero el contenido se pegaba a los cantos. En el teléfono aprieta el ancho. */
@media (min-width: 620px) {
  .bf-centered .box {
    padding: 2rem;
  }
}

.bf-centered .box.wide {
  --bf-door-card-w: 38.75rem;
}

/* Lo de dentro aparece a la vez que el cristal se forma, sin moverse. */
.bf-auth .card > *,
.bf-centered .box > * {
  animation: bf-door-fade 1.2s var(--bf-door-ease) 0.08s both;
}

/* El ANTETÍTULO de la caja: lo que antes era la segunda línea de la marca («Primera
   puesta en marcha», la instalación). Pequeño, espaciado y en el acento de texto. */
.bf-centered .eyebrow {
  margin-bottom: 0.375rem;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--bf-accent-text);
}

/* El icono del título de la tarjeta, suelto y en la tinta del texto: acompaña al
   título sin competir con el botón, que es lo único naranja de la tarjeta. */
.bf-auth .card h1 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.bf-auth .card h1 .h-ico {
  display: inline-grid;
  place-items: center;
  flex: none;
  color: var(--bf-surface-fg);
}

.bf-auth .card h1 .h-ico > svg {
  width: 1.125rem;
  height: 1.125rem;
}

.bf-auth .card h1,
.bf-centered h1 {
  font-size: var(--bf-text-head);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* La entradilla, en la letra de los rótulos y apagada: aquí no compite con nada, y
   en el cuerpo de texto se leía como un párrafo más del formulario. */
.bf-auth .card .lead,
.bf-centered .lead {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  line-height: 1.5;
  margin-top: 0.375rem;
}

/* El encabezado se separa un escalón más de lo que viene detrás: el título con su
   entradilla es la puerta de la tarjeta, no su primer campo. */
.bf-auth .card > div:has(> h1),
.bf-centered .box > div:has(> h1) {
  margin-bottom: 0.5rem;
}

/* LOS PANELES DE LA TARJETA (chore 90.609): iniciar sesión, recuperar la contraseña
   y el resultado, en la MISMA tarjeta. Comparten celda para poder cruzarse; cada uno
   mide lo suyo (`start`) y reparte a sus hijos como la tarjeta. Los ocultos no ocupan
   nada: sin JavaScript sólo existe el primero. */
.bf-door-panes {
  display: grid;
}

.bf-door-pane {
  grid-area: 1 / 1;
  align-self: start;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
  min-width: 0;
  /* El que LLEGA (chore 90.619): aparece algo después de que el otro empiece a irse y
     sin prisa, con la misma curva y el mismo recorrido que el alto de la tarjeta. */
  transition:
    opacity var(--bf-pane-dur) var(--bf-ease-side) calc(var(--bf-dur-fast) / 2),
    transform var(--bf-pane-dur) var(--bf-ease-side);
}

.bf-door-pane[hidden] {
  display: none;
}

.bf-door-pane > div:has(> h1) {
  margin-bottom: 0.5rem;
}

/* El resultado se LEE de un vistazo, como la confirmación aparte: centrado. */
.bf-door-pane.center,
.bf-door-result {
  align-items: center;
  text-align: center;
}

.bf-door-result {
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.bf-door-pane.center > .bf-btn {
  align-self: stretch;
}

/* El título de la tarjeta es flexible (lleva icono): centrado, también su texto. Y
   recibe el foco para que se lea, no para pulsarlo: sin aro. */
.bf-door-result h1 {
  justify-content: center;
}

.bf-door-result h1:focus {
  outline: none;
}

/* EL CAMBIO: la tarjeta va del alto de un panel al del otro mientras los dos se
   cruzan en FUNDIDO, con un desplazamiento de apenas unos píxeles hacia donde se
   avanza —hacia delante, de derecha a izquierda; al volver, al revés—. Recorta sólo
   en vertical, para que el alto mande sin cortar los anillos de foco de los cantos.
   Suave y sin rebote (chore 90.619): la 90.609 cruzaba 16 px con la curva de salida,
   que hace casi todo el recorrido en los primeros 100 ms y se lee como un tirón; aquí
   alto, recorrido y fundido van con la curva simétrica del sistema, sin sobrepaso, y
   algo más despacio que una entrada desde un borde —medio segundo—. Los derivados
   de los tokens heredan su apagado: con menos movimiento valen casi nada. */
.bf-door-panes {
  --bf-pane-dur: calc(var(--bf-dur-side) * 1.4);
}

.bf-door-panes[data-switching] {
  --bf-pane-shift: 0.375rem;
  overflow-x: visible;
  overflow-y: clip;
  transition: height var(--bf-pane-dur) var(--bf-ease-side);
}

.bf-door-panes[data-dir="back"] {
  --bf-pane-shift: -0.375rem;
}

/* El que SE VA se apaga antes —en la mitad del recorrido— para que los dos textos no
   se lean superpuestos; su desplazamiento sigue el del alto. */
.bf-door-pane[data-leaving] {
  opacity: 0;
  transform: translateX(calc(-1 * var(--bf-pane-shift)));
  pointer-events: none;
  transition:
    opacity calc(var(--bf-pane-dur) / 2) var(--bf-ease-side),
    transform var(--bf-pane-dur) var(--bf-ease-side);
}

.bf-door-pane[data-entering] {
  opacity: 0;
  transform: translateX(var(--bf-pane-shift));
  transition: none;
}

/* Con menos movimiento el módulo cambia en seco; esto cubre lo que quede a medias. */
@media (prefers-reduced-motion: reduce) {
  .bf-door-pane,
  .bf-door-panes[data-switching] {
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-door-pane,
:root[data-reduce~="motion"] .bf-door-panes[data-switching] {
  transition: none;
}

/* EL FORMULARIO: una columna y el mismo aire entre todo. */
.bf-door .bf-form {
  display: grid;
  gap: 1.25rem;
}

/* LOS CAMPOS, LOS DE DENTRO —que son los de Hugin—: en la puerta no hay talla grande.
   Quien entra ve estos dos y a los tres segundos ve veinte más, y tienen que ser los
   mismos. Lo que pasa a 44 px es el botón (abajo): es el final del recorrido. */
.bf-door .bf-field input.bf-input.lg {
  height: var(--bf-field-height);
  padding: var(--bf-field-padding);
  font-size: var(--bf-field-font-size);
}

/* LOS BLOQUES DE UN FORMULARIO LARGO («Configura esta instalación»). Hugin no usa
   <fieldset>: lo más cercano son las tarjetas de sección de sus Ajustes —velo de
   tarjeta al 54 %, filete de superficie, radio de 0,65 rem, título con su línea de
   para qué es— y la cabecera de sus diálogos, con el icono en el naranja de marca.
   Aquí son las dos cosas juntas, sobre la tarjeta de cristal. El <legend> flota
   para que se pinte DENTRO de la caja y no montado en el filete. */
.bf-fset {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  min-width: 0;
  margin: 0;
  padding: 1rem;
  border: 1px solid var(--bf-border);
  border-radius: 0.65rem;
  background-color: color-mix(in srgb, var(--bf-surface-bg) 54%, transparent);
}

.bf-fset > legend {
  float: left;
  width: 100%;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0;
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-fset > legend .fic {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--bf-radius-btn);
  background-color: color-mix(in srgb, var(--bf-accent) 14%, transparent);
  color: var(--bf-accent);
}

.bf-fset > legend .fic > svg {
  width: 1.05rem;
  height: 1.05rem;
}

/* La línea de para qué es el bloque, pegada a su título. */
.bf-fset-hint {
  margin: -0.5rem 0 0;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

/* EL OJO de la contraseña: un botón dentro del campo, a la derecha, que el módulo
   reveal.js enseña (sin él nace oculto). Dibuja lo que VA a pasar: el ojo abierto
   para ver, el tachado para ocultar. El campo reserva su hueco para que el texto no
   se meta debajo. */
.bf-reveal {
  position: relative;
}

.bf-field .bf-reveal > input:is([type="password"], [type="text"]) {
  padding-right: 2.25rem;
}

.bf-reveal-btn {
  position: absolute;
  top: 0;
  right: 0;
  display: grid;
  place-items: center;
  width: 2.125rem;
  height: 100%;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-input);
  background: none;
  color: var(--bf-muted);
  cursor: pointer;
  transition: color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-reveal-btn[hidden] {
  display: none;
}

.bf-reveal-btn:hover {
  color: var(--bf-surface-fg);
}

.bf-reveal-btn > svg {
  width: 0.95rem;
  height: 0.95rem;
}

.bf-reveal-btn .hide,
.bf-reveal-btn[aria-pressed="true"] .show {
  display: none;
}

.bf-reveal-btn[aria-pressed="true"] .hide {
  display: block;
}

/* Una nota centrada bajo el botón (lo que pasa al pulsarlo). */
.bf-hint.center {
  text-align: center;
  justify-content: center;
}

/* La casilla, en la letra de los rótulos, y la línea entera con la mano: su texto es
   parte del objetivo que se pulsa (la etiqueta envuelve al control). */
.bf-door .bf-check,
.bf-door .bf-check input[type="checkbox"] {
  cursor: pointer;
}

.bf-door .bf-check {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-door .bf-check input[type="checkbox"]:not(.bf-switch):not(:hover) {
  background-color: var(--bf-door-field-bg);
}

/* Y su filete, un susurro: en la puerta los campos van sin filete y la casilla, con
   el suyo al 32 %, era lo más marcado del formulario. La dibuja su relleno; marcada,
   recupera el filete de siempre, y el foco sigue siendo el aro de acento. */
.bf-door .bf-check input[type="checkbox"]:not(.bf-switch):not(:checked):not(:indeterminate) {
  border-color: color-mix(in srgb, var(--bf-muted) 14%, transparent);
}

/* «Recuérdame» y «¿Has olvidado tu contraseña?» en UNA fila bajo los campos, cada
   uno en su extremo. El enlace se empuja a la derecha también cuando va solo (la
   instalación no ofrece sesión persistente); si no caben, baja a su propia línea. */
.bf-door .bf-signin-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
}

.bf-door .bf-signin-row .bf-authlink {
  margin-left: auto;
}

/* LOS CAMPOS SOBRE EL CRISTAL, COMO SE VEN EN HUGIN. En Hugin el campo va sobre una
   tarjeta y se lee como una caja RELLENA; aquí, sobre un cristal casi transparente y
   un escenario casi negro, su velo desaparece y sólo se veía el filete: un rectángulo
   dibujado. Así que en la puerta el campo lleva el relleno que tiene sobre una
   tarjeta de Hugin y ningún filete en reposo; el foco (el CTA) y el error (el rojo)
   siguen siendo su canto. */
.bf-door .bf-field :is(input:not([type="checkbox"]):not([type="radio"]), textarea),
.bf-door .bf-code input,
.bf-door .bf-code-one input {
  background-color: var(--bf-door-field-bg);
}

.bf-door .bf-field :is(input:not([type="checkbox"]):not([type="radio"]), textarea):not(:focus):not(.err):not([aria-invalid="true"]),
.bf-door .bf-code input:not(:focus):not([aria-invalid="true"]),
.bf-door .bf-code-one input:not(:focus):not([aria-invalid="true"]) {
  border-color: transparent;
}

/* EL BOTÓN, más alto que el de dentro y a todo el ancho: aquí hay UNA acción y es el
   final del recorrido. A 2,75 rem pesa lo que pesa y es además el objetivo táctil
   cómodo de WCAG (SC 2.5.5). Un cuarto de rem más separado que el resto, para que se
   lea como el final de la fila y no como un campo más. */
.bf-door .bf-btn.lg,
.bf-door .bf-idp {
  height: 2.75rem;
}

.bf-door .bf-form > .bf-btn {
  margin-top: 0.25rem;
}

.bf-door .bf-btn > svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* EL HOVER DE LA PUERTA ES DISCRETO (chore 90.585): el CTA no se levanta ni echa
   luz —la caída de su propio color de la 90.384 se leía, sobre el cristal y la
   escena, como un halo que salía del botón—. Queda el cambio de tono de siempre y
   el ICONO de la acción se adelanta tres píxeles con una curva que frena despacio,
   como en Hugin. El pulsado y el aro del foco no cambian. */
.bf-door .bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) {
  transform: none;
  box-shadow: none;
}

.bf-door .bf-btn.primary > svg {
  transition: transform 0.36s cubic-bezier(0.22, 1, 0.36, 1);
}

.bf-door .bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) > svg,
.bf-door .bf-btn.primary:focus-visible > svg {
  transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
  .bf-door .bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) > svg,
  .bf-door .bf-btn.primary:focus-visible > svg {
    transform: none;
  }
}

:root[data-reduce~="motion"] .bf-door .bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) > svg,
:root[data-reduce~="motion"] .bf-door .bf-btn.primary:focus-visible > svg {
  transform: none;
}

/* EL ICONO, CON AIRE (chore 90.587): a 6 px del texto, el avance de tres píxeles
   del hover casi lo tocaba y no se leía como movimiento. A 10 px se ve adelantarse.
   El conjunto sigue centrado: el hueco es del flex, no un margen del icono. */
.bf-door .bf-btn.primary {
  gap: 0.625rem;
}

.bf-door .bf-idp {
  justify-content: center;
}

/* ── La marca: el logotipo con su brillo y el nombre ───────────────────────────
   Preside, porque aquí no hay barra lateral que la lleve: es la única pieza que dice
   en qué producto se está a punto de entrar. Sube al entrar. */
.bf-door .bf-mark-lg {
  gap: 0.625rem;
  color: var(--bf-surface-fg);
  animation: bf-door-rise 1.1s var(--bf-door-ease) both;
}

/* MÁS GRANDE Y CON LUZ: es la pieza que preside, y sobre el escenario el logotipo
   echa un resplandor del naranja de marca, el mismo que la ola. */
.bf-door .bf-mark-lg {
  gap: 0.875rem;
}

.bf-door .bf-mark-lg .bf-logo {
  width: 3.25rem;
  height: 3.25rem;
  filter: var(--bf-door-logo-glow);
}

/* El acento del logotipo, un punto más hondo sobre papel: es lo único de la puerta
   que carga con un mínimo de contraste (3:1 para un glifo) y la ola no. */
.bf-door .bf-mark-lg .bf-logo .fire {
  stroke: var(--bf-door-mark);
}

.bf-door .bf-mark-lg b {
  font-size: 2.25rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1;
}


/* ── El pie de la tarjeta: DÓNDE se está entrando ───────────────────────────────
   Las PÍLDORAS de la promesa, en pequeño: cada dato —el estado, la instalación, la
   versión— es una cápsula con su marca en un círculo, en una fila centrada al pie de
   la columna, y debajo la firma de la organización. Sin ficha que los envuelva: la
   columna ya es el cristal, y otra caja dentro pesaba más que los propios datos.
   La lista de datos (<dl>) sigue ahí para el lector de pantalla, que los lee como
   pares; los rótulos, además, al apuntar (title). */
.bf-auth .foot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.875rem;
  width: 100%;
  max-width: 32.5rem;
  margin-top: 2rem;
  animation: bf-door-fade 1.2s var(--bf-door-ease) 0.3s both;
}

.bf-auth .foot .facts {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  max-width: 100%;
  margin: 0;
}

.bf-auth .foot .facts > div {
  display: flex;
  min-width: 0;
  max-width: 100%;
}

/* Cada dato, una píldora con el material de las de la promesa pero SIN su desenfoque:
   dentro de la columna de cristal, un segundo cristal no ve la ola —sólo el velo de la
   columna— y desenfocar eso no aporta nada. */
.bf-auth .foot dd {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin: 0;
  padding: 0.25rem 0.75rem 0.25rem 0.25rem;
  border: 1px solid var(--bf-door-card-edge);
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) var(--bf-door-pill-veil), transparent);
  box-shadow: var(--bf-door-card-bevel);
  font-size: var(--bf-text-label);
  font-weight: 500;
  color: var(--bf-surface-fg);
  white-space: nowrap;
  overflow: hidden;
}

/* La marca de cada dato: el círculo neutro de las píldoras de la promesa. */
.bf-auth .foot .glyph {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.375rem;
  height: 1.375rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
}

.bf-auth .foot .glyph > svg {
  width: 0.75rem;
  height: 0.75rem;
  stroke-width: 2.2;
}

/* La instalación puede tener un nombre largo: se recorta, no rompe la fila. */
.bf-auth .foot b {
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 600;
}

/* El estado lleva el único color del pie: el LED, SOLO el punto —sin círculo teñido
   detrás—, en el hueco que las otras píldoras dan a su icono, para que las tres
   midan lo mismo. Y VIVO: una onda sale de él cada pocos segundos. */
.bf-auth .foot .bf-led {
  display: inline-grid;
  place-items: center;
  width: 1.375rem;
  height: 1.375rem;
  background: transparent;
}

.bf-auth .foot .bf-led::after {
  content: "";
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: currentColor;
  animation: bf-door-led 2.6s ease-out infinite;
}

@keyframes bf-door-led {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 50%, transparent); }
  70%,
  100% { box-shadow: 0 0 0 0.3rem color-mix(in srgb, currentColor 0%, transparent); }
}

/* La versión, con cifras de ancho fijo. */
.bf-auth .foot [data-glyph="tag"] dd {
  font-variant-numeric: tabular-nums;
}

/* La firma de la organización, debajo y en voz baja. El sangrado compensa el aire
   que el espaciado deja tras la última letra: centrada, si no, cae a la izquierda. */
.bf-auth .foot .org {
  padding-left: 0.28em;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--bf-muted) 80%, transparent);
}

/* El DERECHO DE AUTOR (90.590), lo último dentro de la tarjeta: tras un filete que se
   desvanece hacia los cantos, centrado y en la voz más baja de la tarjeta. */
.bf-auth .card .copyright {
  margin: auto 0 0;
  padding-top: 1rem;
  border-top: 1px solid transparent;
  border-image: linear-gradient(90deg, transparent, var(--bf-door-card-edge), transparent) 1;
  font-size: 0.6875rem;
  letter-spacing: 0.02em;
  text-align: center;
  color: color-mix(in srgb, var(--bf-muted) 85%, transparent);
}

/* ── Los errores de la puerta ──────────────────────────────────────────────────
   Discretos: el campo ya lo dice con su canto rojo, así que ninguna frase lleva
   caja. La de debajo del campo va como en Hugin —su icono delante y la frase,
   entrando desde el campo— en un rojo templado con la tinta apagada. El aviso de
   arriba, lo mismo con un punto más de presencia: un velo del rojo que se desvanece
   hacia la derecha, sin tarjeta, borde ni raya. El icono lo pone la hoja cuando el
   marcado no lo trae (el login). */
.bf-door .bf-hint.bad {
  align-items: flex-start;
  gap: 0.375rem;
  padding: 0 0.125rem;
  color: color-mix(in srgb, var(--bf-danger-text) 72%, var(--bf-muted));
  font-size: 0.75rem;
  line-height: 1.45;
}

.bf-door .bf-hint.bad:not(:has(> svg))::before,
.bf-door .bf-msg.err::before {
  content: "";
  flex: none;
  width: 0.75rem;
  height: 0.75rem;
  margin-top: 0.15625rem;
  background-color: color-mix(in srgb, var(--bf-danger) 85%, transparent);
  -webkit-mask: var(--bf-alert-mark) center / contain no-repeat;
  mask: var(--bf-alert-mark) center / contain no-repeat;
}

.bf-door .bf-hint.bad > svg {
  width: 0.75rem;
  height: 0.75rem;
  color: color-mix(in srgb, var(--bf-danger) 85%, transparent);
}

.bf-door .bf-msg.err {
  align-items: flex-start;
  gap: 0.5rem;
  padding: 0.5rem 0.75rem;
  border: 0;
  border-radius: 0.5rem;
  background: linear-gradient(90deg, color-mix(in srgb, var(--bf-danger) 7%, transparent), transparent 75%);
  box-shadow: none;
  color: color-mix(in srgb, var(--bf-danger-text) 66%, var(--bf-surface-fg));
  font-size: 0.75rem;
  line-height: 1.5;
  animation: bf-field-error-in var(--bf-dur-base) var(--bf-ease-out);
}

/* EL ENLACE DE VUELTA («Volver al inicio de sesión»): un botón FANTASMA con su caret,
   que se desplaza hacia donde lleva al apuntarlo. Se lee como una salida, no como
   una nota al pie; y casi transparente, para no competir con la acción principal. */
.bf-back {
  display: flex;
  justify-content: center;
}

.bf-backlink {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.875rem 0.375rem 0.625rem;
  border: 1px solid color-mix(in srgb, var(--bf-surface-fg) 10%, transparent);
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) 12%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  font-weight: 500;
  text-decoration: none;
  transition:
    color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-backlink > svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-backlink:hover,
.bf-backlink:focus-visible {
  border-color: color-mix(in srgb, var(--bf-surface-fg) 18%, transparent);
  background-color: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  color: var(--bf-surface-fg);
  text-decoration: none;
}

.bf-backlink:hover > svg,
.bf-backlink:focus-visible > svg {
  transform: translateX(-2px);
}

/* ── La entrada ────────────────────────────────────────────────────────────────
   La tarjeta no aparece: se FORMA. Parte de cristal plano —sin velo, canto ni
   sombra— y cuaja; lo de dentro llega a la vez, sin moverse. La marca sube. */
@keyframes bf-door-rise {
  from { opacity: 0; transform: translateY(0.5rem); }
  to { opacity: 1; transform: none; }
}

@keyframes bf-door-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

@keyframes bf-door-seg-pop {
  from { opacity: 0; transform: scale(0.55); }
  to { opacity: 1; transform: none; }
}

@keyframes bf-door-glass-in {
  from {
    background-color: transparent;
    border-color: transparent;
    box-shadow: none;
    -webkit-backdrop-filter: var(--bf-door-filter-flat);
    backdrop-filter: var(--bf-door-filter-flat);
  }
}

/* Sin cristal —el navegador no sabe, o la persona pidió menos transparencia—, la
   tarjeta es maciza: sobre un fondo con una luz, un cuadro translúcido sin desenfoque
   mezcla el texto con lo de detrás. Las tres vías, como en tokens.css. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-auth .form,
  .bf-centered .box {
    background-color: var(--bf-surface-bg);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-auth .form,
  .bf-centered .box {
    background-color: var(--bf-surface-bg);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

:root[data-reduce~="transparency"] .bf-auth .form,
:root[data-reduce~="transparency"] .bf-centered .box {
  background-color: var(--bf-surface-bg);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* Con menos movimiento no hay entradas ni fundidos: todo sale ya puesto, y el cielo
   es un solo fotograma (lo decide door-sky.ts). */
@media (prefers-reduced-motion: reduce) {
  .bf-door-wave .ribbon path,
  .bf-door-wave .bloom {
    animation: none;
  }

  .bf-door-plate,
  .bf-door-orbit {
    transition: none;
  }

  .bf-door .bf-mark-lg,
  .bf-auth .art .pitch-eyebrow,
  .bf-auth .art h2,
  .bf-auth .art p,
  .bf-auth .art .perks li,
  .bf-auth .foot .bf-led::after,
  .bf-auth .form,
  .bf-auth .card > *,
  .bf-auth .foot,
  .bf-centered .box,
  .bf-centered .box > *,
  .bf-prefs .group,
  .bf-prefs button {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-door-wave .ribbon path,
:root[data-reduce~="motion"] .bf-door-wave .bloom {
  animation: none;
}

:root[data-reduce~="motion"] .bf-door-plate,
:root[data-reduce~="motion"] .bf-door-orbit {
  transition: none;
}

:root[data-reduce~="motion"] .bf-door .bf-mark-lg,
:root[data-reduce~="motion"] .bf-auth .art .pitch-eyebrow,
:root[data-reduce~="motion"] .bf-auth .art h2,
:root[data-reduce~="motion"] .bf-auth .art p,
:root[data-reduce~="motion"] .bf-auth .art .perks li,
:root[data-reduce~="motion"] .bf-auth .foot .bf-led::after,
:root[data-reduce~="motion"] .bf-auth .form,
:root[data-reduce~="motion"] .bf-auth .card > *,
:root[data-reduce~="motion"] .bf-auth .foot,
:root[data-reduce~="motion"] .bf-centered .box,
:root[data-reduce~="motion"] .bf-centered .box > *,
:root[data-reduce~="motion"] .bf-prefs .group,
:root[data-reduce~="motion"] .bf-prefs button {
  animation: none;
}

.bf-sep {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

.bf-sep i {
  flex: 1;
  height: 1px;
  background: var(--bf-border);
}

/* Proveedor de identidad federado: un botón por proveedor. Es un MANDO, y desde la
   chore 90.261 se viste como el neutro del catálogo (90.254): el velo del mando con
   su filete, el radio de mando, la talla grande del botón de «Entrar» que tiene al
   lado (.bf-btn.lg) —dos alturas distintas en la misma columna se leían como dos
   clases de cosa— y el realce apilado sobre el velo, sin tocar el canto (S-5). */
.bf-idp {
  height: 2.25rem;
  border: 1px solid var(--bf-button-line);
  border-radius: var(--bf-button-radius);
  display: flex;
  align-items: center;
  gap: 0.6875rem;
  padding: 0 0.875rem;
  font-size: var(--bf-text-body);
  font-family: inherit;
  font-weight: 600;
  background-color: var(--bf-button-bg);
  color: var(--bf-button-fg);
  text-decoration: none;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
}

/* Se pinta como botón pero es un <a> (LoginPage): sin esto, `a:hover` le subraya el
   nombre del proveedor y deja de parecer un botón justo al apuntarlo (chore 90.215). */
.bf-idp:hover {
  text-decoration: none;
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-idp:active {
  background-image:
    linear-gradient(var(--bf-hover), var(--bf-hover)),
    linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-idp .mark {
  width: 1.125rem;
  height: 1.125rem;
  border-radius: var(--bf-radius-xs);
  flex: none;
}

/* Las salidas de la puerta («he olvidado mi contraseña», «volver a iniciar sesión»).
   Con el vestido de Latanur se comportan como un enlace: en reposo van apagadas para
   no competir con el botón, y al apuntarlas o recibir el foco suben de tinta y se
   subrayan. El subrayado SE DIBUJA desde la izquierda —crece en el sentido en que se
   lee— y con `transform`, que lo mueve el compositor sin recalcular la caja. */
.bf-authlink {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

a.bf-authlink,
.bf-authlink a {
  position: relative;
  color: var(--bf-muted);
  text-decoration: none;
  transition: color var(--bf-dur-base) var(--bf-ease-out);
}

a.bf-authlink::after,
.bf-authlink a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.15em;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--bf-dur-slow) var(--bf-ease-out);
}

a.bf-authlink:hover,
a.bf-authlink:focus-visible,
.bf-authlink a:hover,
.bf-authlink a:focus-visible {
  color: var(--bf-surface-fg);
  text-decoration: none;
}

a.bf-authlink:hover::after,
a.bf-authlink:focus-visible::after,
.bf-authlink a:hover::after,
.bf-authlink a:focus-visible::after {
  transform: scaleX(1);
}

/* La salida de un reto va centrada bajo el bloque. Es del uso, no de la pieza. */
.bf-authlink.center {
  text-align: center;
}

/* Selección de IdP federado (05.004 · chore 90.064): la maqueta escribe el
   separador y los proveedores sueltos dentro del formulario; aquí van agrupados en
   una <nav> rotulada —que es lo que el lector de pantalla necesita para anunciar la
   sección— con el MISMO hueco que reparte el chasis, de modo que se ven igual. */
.external {
  display: flex;
  flex-direction: column;
  gap: 0.9375rem;
}

/* El rótulo de esa sección ES el texto del separador: se escribe como <h2> por
   jerarquía del documento, no por aspecto, así que no toma el suyo. */
.bf-sep h2 {
  font-size: inherit;
  font-weight: 400;
  color: inherit;
}

/* Caja centrada, variante CENTRADA (chore 90.064): para lo que se lee de un
   vistazo y no se rellena —el cierre de sesión, un enlace caducado—. Un formulario
   nunca va aquí: las etiquetas de campo se leen mucho peor centradas. La caja y su
   cristal son los de la puerta (arriba). */
.bf-centered .box.center {
  text-align: center;
  align-items: center;
}

/* El rótulo de sección dentro de la caja no necesita su aire de arriba (chore
   90.067): en la ficha de administración ese relleno compensa el carril lateral, y
   aquí la caja ya reparte a sus hijos con un hueco fijo. */
.bf-centered .box .bf-frail {
  padding-top: 0;
}

/* ── Cierre de sesión global (03.006 · chore 90.064) ───────────────────────────
   Una línea por instancia de la sesión: LED de estado + nombre + resultado a la
   derecha. La maqueta la resolvía con estilos en línea. */

.bf-logout-list {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 9px;
  list-style: none;
  margin: 0;
  padding: 0;
  text-align: left;
}

.bf-logout-list li {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: var(--bf-table-row-font-size);
}

.bf-logout-list .state {
  margin-left: auto;
  color: var(--bf-dim);
  font-size: var(--bf-legend-font-size);
}

/* Los iframes que cierran la sesión en cada RP no se ven: lo que el usuario lee es
   la lista de arriba, no el mecanismo. */
.bf-logout-frame {
  display: none;
  width: 0;
  height: 0;
  border: 0;
}

/* ── Preferencias sin sesión (chore 90.095) ────────────────────────────────────
   Tema e idioma de quien todavía no ha entrado, arriba a la derecha y fuera del
   flujo: no son de la tarjeta —no forman parte de entrar— sino del marco. Con el
   vestido de Latanur son dos SEGMENTADOS con el cristal de lo que flota (el mismo
   velo, desenfoque y sombra): sobre la ola no hay panel detrás, y sin superficie
   propia un segmentado se queda flotando sin caja. El tema va por icono y el idioma
   por su código; el nombre entero, en texto para lectores y en el `title`. Entran
   formándose, y cada botón con un pequeño rebote escalonado. */
/* Un cuarto de rem del borde de arriba: más pegados que en Latanur (0,75), a petición —son
   cromo del navegador y no deben competir con la marca, que va a 1,5 rem—. */
.bf-prefs {
  position: fixed;
  top: 0.25rem;
  right: 0.75rem;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0;
}

.bf-prefs .group {
  display: flex;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-btn);
  overflow: hidden;
  /* Bastante más transparentes que el cristal de lo que flota (74 %) y SIN SOMBRA:
     sobre la ola no hay datos que tapar, y con el velo y la sombra de un menú
     flotante pesaban más que la propia tarjeta. */
  background-color: color-mix(in srgb, var(--bf-canvas) 32%, transparent);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  animation: bf-door-glass-in 0.5s cubic-bezier(0.22, 0.61, 0.36, 1) both;
}

.bf-prefs button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.6rem;
  padding: 0 0.75rem;
  border: 0;
  background: transparent;
  color: var(--bf-muted);
  font: inherit;
  font-size: 0.8125rem;
  white-space: nowrap;
  gap: 0.25rem;
  cursor: pointer;
  transform-origin: left center;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
  animation: bf-door-seg-pop 0.56s cubic-bezier(0.3, 1.45, 0.5, 1) both;
}

.bf-prefs button + button {
  border-left: 1px solid var(--bf-border);
}

.bf-prefs button > svg {
  width: 0.85rem;
  height: 0.85rem;
}

/* Sin realce propio en la vigente: la que ya está puesta no cambia al apuntarla (Latanur). */
.bf-prefs button:hover:not([aria-current="true"]) {
  background-color: color-mix(in srgb, var(--bf-hover) 45%, transparent);
  color: var(--bf-surface-fg);
}

/* La opción vigente no se marca sólo con color (M-2): lleva `aria-current`, que es
   lo que la anuncia, y de ahí cuelga el realce. */
.bf-prefs button[aria-current="true"] {
  background-color: var(--bf-door-seg-selected);
  color: var(--bf-accent);
  font-weight: 600;
}

/* EL ESCALÓN de Latanur, retardo a retardo. Por TIPO y no por posición: el formulario
   empieza con dos campos ocultos (el antiforgery y la vuelta), así que el grupo del
   tema nunca es el «primer hijo» — con `:first-child` sus tres botones salían a la
   vez, sin escalón. */
.bf-prefs .group:first-of-type,
.bf-prefs .group:first-of-type > :nth-child(1) { animation-delay: 0.18s; }
.bf-prefs .group:first-of-type > :nth-child(2) { animation-delay: 0.265s; }
.bf-prefs .group:first-of-type > :nth-child(3) { animation-delay: 0.35s; }
.bf-prefs .group:last-of-type,
.bf-prefs .group:last-of-type > :nth-child(1) { animation-delay: 0.435s; }
.bf-prefs .group:last-of-type > :nth-child(2) { animation-delay: 0.52s; }

/* Texto sólo para lectores de pantalla: el nombre entero de una opción que se ve
   como icono o como código. */
.bf-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Circuito de contraseña (02.003/02.004 · chore 90.065) ─────────────────────
   Medidor de la contraseña nueva: cuatro tramos que el módulo /js/account/
   password-meter.js enciende con `data-level`. Nace `hidden` y sólo lo revela ese
   módulo: sin JavaScript no hay medidor, en vez de una tira muerta. El tono lo
   pone el NIVEL, no cada tramo por su cuenta (la maqueta los mezclaba con estilos
   en línea y una tira de dos colores no dice nada legible). */

.bf-meter {
  display: flex;
  gap: 4px;
  margin-top: 2px;
}

.bf-meter i {
  flex: 1;
  height: 3px;
  border-radius: 2px;
  background: var(--bf-border);
}

.bf-meter[data-level="1"] i:nth-child(-n + 1),
.bf-meter[data-level="2"] i:nth-child(-n + 2) {
  background: var(--bf-warning);
}

.bf-meter[data-level="3"] i:nth-child(-n + 3),
.bf-meter[data-level="4"] i {
  background: var(--bf-success);
}

/* La consecuencia se escribe bajo el botón que la provoca, centrada con él. */
.bf-hint.center {
  text-align: center;
}

/* Sello de las pantallas que sólo se leen: el visto de lo que salió bien y el
   aviso de lo que ya no sirve. Decoración —el encabezado dice lo mismo—, así que
   el dibujo va aquí y el marcado se queda en un div vacío. */
.bf-seal {
  width: 54px;
  height: 54px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 6px 0;
  flex: none;
  border: 1px solid;
}

.bf-seal.ok {
  background: var(--bf-success-soft);
  border-color: color-mix(in srgb, var(--bf-success) 30%, transparent);
}

/* El visto es medio recuadro girado: dos lados de una caja de 16×9 a −45°. */
.bf-seal.ok::after {
  content: "";
  width: 16px;
  height: 9px;
  border: 2px solid var(--bf-success);
  border-top: 0;
  border-right: 0;
  transform: rotate(-45deg);
  margin-top: -4px;
}

.bf-seal.warn {
  background: var(--bf-warning-soft);
  border-color: color-mix(in srgb, var(--bf-warning) 30%, transparent);
}

.bf-seal.warn::after {
  content: "!";
  color: var(--bf-warning-text);
  font-size: 26px;
  font-weight: 600;
  line-height: 1;
}

/* Marca: las dos bandas y el horizonte (el dibujo, en BifrostMark.cs; el porqué, en
   wwwroot/favicon.svg). SIN FONDO: el azulejo de la familia es sólo del favicon. Aquí el
   dibujo va suelto sobre lo que tenga debajo, así que la banda clara y el horizonte toman
   la tinta del texto de al lado (currentColor) y se leen igual sobre pizarra, superficie
   clara u oscura. La banda de fuera lleva el acento de la instalación (chore 90.061). */
.bf-logo {
  width: 30px;
  height: 30px;
  flex: none;
}

.bf-logo > svg {
  display: block;
  width: 100%;
  height: 100%;
}

.bf-logo .band {
  stroke: currentColor;
}

.bf-logo .fire {
  stroke: var(--bf-accent);
}

.bf-mark-lg {
  display: flex;
  align-items: center;
  gap: 11px;
}

.bf-mark-lg .bf-logo {
  width: 34px;
  height: 34px;
}

.bf-mark-lg b {
  font-size: 17px;
  font-weight: 600;
}

.bf-mark-lg span {
  font-size: 11px;
  color: var(--bf-dim);
  display: block;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Código del segundo factor (reto del login y confirmación del alta): la entrada
   tiene DOS FORMAS y el corte de 620 elige (chore 90.122 · MF-4).

   Por qué dos formas y no una. Seis objetivos táctiles de 44 px con sus cinco
   huecos de 8 piden 304 px LIBRES. El cromo por encima de las casillas es fijo
   —cuerpo del shell 16+16, marco de página 12+12, filete de la tarjeta 1+1 y
   relleno de la tarjeta 12+12 = 82— y la chore 90.099 ya lo bajó todo lo que se
   podía: a 390 salen 44,7 px por casilla (cumple), a 375 42,2 y a 360 39,7. La
   aritmética no da más de sí: ni con relleno cero caben seis casillas de 44 a 360
   dentro de una tarjeta (360 − 32 − 2 = 326 y las casillas piden 304). Así que en
   teléfono no se aprieta más el cromo: CAMBIA LA FORMA de la entrada a un solo
   campo a ancho completo, cuyo objetivo táctil ya no depende del ancho que sobre.

   Por qué las dos viajan en el HTML y las gobierna esta media query. Adivinar el
   ancho en el servidor por user-agent es frágil, y el ancho puede cambiar después
   de servir la página (rotar el teléfono, partir la ventana). Que se envíe UN solo
   código —también sin JavaScript— sale de tres cosas, explicadas en la cabecera de
   MfaCodeMarkup.cs: los siete campos comparten el nombre `code` y el servidor los
   une SIN separador, con lo que la forma escondida aporta la cadena vacía; ninguna
   restricción de formulario puede fallar en la forma escondida (un control inválido
   dentro de un `display:none` no es enfocable y el navegador se niega a enviar el
   formulario entero, así que ni `required` ni `pattern` en el campo único); y
   `display:none` saca la forma que no toca del árbol de accesibilidad, así que el
   lector de pantalla anuncia una sola. */
.bf-code-entry {
  /* Ocupa el ancho de su hueco aunque el padre no lo estire (el formulario de un
     paso alinea a `flex-start`): sin esto la entrada se dimensiona por su contenido
     —321 px— y la sexta casilla se sale de la tarjeta en un teléfono. */
  align-self: stretch;
}

/* ── Forma ESTRECHA (base, mobile-first MF-2): un campo para los seis dígitos ── */
.bf-code-one input {
  width: 100%;
  height: 54px;
  border: 1px solid var(--bf-field-edge);
  border-radius: var(--bf-radius-m);
  background: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  padding: 0;
  text-align: center;
  font-family: inherit;
  font-size: 22px;
  font-variant-numeric: tabular-nums;
  /* Los seis dígitos se leen sueltos, como en las casillas. El sangrado compensa el
     hueco que `letter-spacing` deja DETRÁS del último dígito y devuelve el centrado. */
  letter-spacing: 0.5em;
  text-indent: 0.5em;
}

.bf-code-one input:focus {
  outline: none;
  border-color: var(--bf-accent);
  /* Mismo anillo doble que el campo (90.147). */
  box-shadow:
    0 0 0 1px var(--bf-surface-fg),
    0 0 0 4px color-mix(in srgb, var(--bf-accent) 20%, transparent);
}

.bf-code-one .bf-hint {
  margin: 6px 0 0;
}

/* ── Forma ANCHA (desde 620): las seis casillas de la maqueta, intactas ──────── */
.bf-code {
  /* La enseña el corte de abajo. En teléfono manda el campo único. */
  display: none;
  gap: 9px;
  /* CENTRADAS en su hueco (chore 90.222). Las seis casillas suman 321 px y no crecen
     más (`max-width: 46px`, la medida de la maqueta), pero el bloque que las contiene
     sí: en el reto del login es la columna entera del panel de acceso —472 px en la
     forma estrecha—, así que los 151 px sobrantes se acumulaban todos a la derecha y
     el código quedaba descolgado del botón «Verificar», que sí ocupa la columna. La
     maqueta se dibuja a 1280 y ahí el hueco también existe: esto la corrige a
     propósito, por el recorrido manual del 2026-09-02. */
  justify-content: center;
}

/* Las casillas ENCOGEN antes que desbordar (MF-5: la página no se desplaza en
   horizontal). Seis cajas de 46 px con sus cinco huecos suman 321, que no siempre
   caben en el tramo medio. Con `flex: 1 1 0` + `max-width` no hace falta otra media
   query: donde hay sitio se quedan en los 46 px de la maqueta y donde no, reparten
   el ancho disponible (MF-2, patrón intrínseco antes que corte). La ALTURA no se
   toca: es la que sostiene el objetivo táctil de 44 px (MF-4). */
.bf-code i,
.bf-code input {
  flex: 1 1 0;
  min-width: 0;
  max-width: 46px;
  height: 54px;
  border: 1px solid var(--bf-field-edge);
  border-radius: var(--bf-radius-m);
  background: var(--bf-field-bg);
  color: var(--bf-surface-fg);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: inherit;
  font-size: 22px;
  font-style: normal;
  font-variant-numeric: tabular-nums;
}

/* La casilla REAL es un <input>: el centrado por flex no le vale. */
.bf-code input {
  display: block;
  text-align: center;
  padding: 0;
}

.bf-code i.on,
.bf-code input:focus {
  outline: none;
  border-color: var(--bf-accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--bf-accent) 20%, transparent);
}

/* La casilla con el foco lleva además la tinta del anillo doble (90.147); la
   `.on` de la maqueta es la casilla RELLENA, que es estado y se queda igual. */
.bf-code input:focus {
  box-shadow:
    0 0 0 1px var(--bf-surface-fg),
    0 0 0 4px color-mix(in srgb, var(--bf-accent) 20%, transparent);
}

.bf-code i.ph {
  color: var(--bf-dim);
}

/* El corte que elige la forma (chore 90.122). Desde 620 mandan las seis casillas de
   la maqueta y el campo único desaparece; a ancho completo la pantalla es la de
   siempre, píxel a píxel (MF-1). */
@media (min-width: 620px) {
  .bf-code-one {
    display: none;
  }

  .bf-code {
    display: flex;
  }
}

/* ═══ 5 · Superposiciones y detalle ══════════════════════════════════════════ */

/* ── El CRISTAL, en un sitio (contrato de diseño §2.4 · chore 90.251) ──────────
   Todo lo que FLOTA sobre la hoja —el menú, el aviso, el buscador, las hojas del
   teléfono, el marco del shell— es la misma clase de pieza, y hasta ahora el
   sistema no sabía pintarla: M-4 decía «sin sombras» y no había ni velo ni
   desenfoque. Esta es la receta, escrita UNA vez: quien flote la hereda y encima
   pone su sitio, su tamaño y su relleno.

   SIN @supports y sin consulta de transparencia. Cuando el navegador no sabe
   desenfocar, o la persona ha pedido menos transparencia, los dos tokens de la
   receta ya vienen reapuntados desde tokens.css y esta pieza sale opaca sin
   enterarse. Repetir aquí la consulta sería la manera de olvidarla en la siguiente.

   SIN filete, tampoco: el canto es el BISEL —la ceja de luz arriba, la sombra
   abajo—, que es lo que hace que una lámina se lea como vidrio. Un borde a plena
   fuerza sobre una superficie translúcida dibuja una caja pegada encima, no algo
   que flota; quien necesite marcar más el canto tiene --bf-glass-edge, que va como
   aro interior y no como borde. La sombra es la corta (--bf-glass-drop): sólo tiene
   que despegar la pieza, porque sostenerla ya lo hacen el desenfoque y el bisel.

   QUIÉN LA HEREDA (chore 90.258): lo dice la LISTA DE SELECTORES de esta regla, no
   una clase en el marcado. El menú «···» de Productos lo pinta el servidor, el
   aviso emergente, la pista y el calendario (chore 90.260) los crea su módulo y el
   rótulo del hito lo pinta su vista: con una clase habría que acordarse en cinco
   sitios —tres de ellos en TypeScript—, y así la receta sigue escrita UNA vez y
   quien la gasta se lee aquí.
   Cada pieza pone después, en su propia regla, su sitio, su medida, su relleno y
   —la pista— un radio más cerrado. `.bf-glass` sigue para lo que se sume sin regla
   propia.

   El DIÁLOGO no está, y a propósito: es macizo (más abajo). Trae su velo detrás,
   que apaga la página para que no compita con la pregunta, y un cristal encima de
   una página ya apagada sólo deja pasar manchas grises debajo del texto. */
.bf-glass,
.bf-more .pop,
.bf-pick.float .pop,
.bf-toast,
.bf-dp {
  background: var(--bf-glass-surface);
  backdrop-filter: var(--bf-glass-filter);
  -webkit-backdrop-filter: var(--bf-glass-filter);
  border-radius: var(--bf-glass-radius);
  box-shadow: var(--bf-glass-drop), var(--bf-glass-bevel);
  color: var(--bf-surface-fg);
}

/* ── LAS PESTAÑAS (chore 90.256 · contrato §3 y §6, M-9) ───────────────────────
   La puesta ya no es un subrayado de 2 px pegado a un filete que cruza la página:
   es una PASTILLA SOBRE UN CARRIL. Lo que marca no es una línea sino una
   superficie, el grupo se lee como un solo mando —«de estas, esta», igual que el
   segmentado— y no necesita filete debajo para saber dónde acaba. Y la pastilla
   VIAJA de la pestaña de antes a la nueva en --bf-dur-glide: es lo que dice que
   las pestañas son un mando y no una fila de enlaces. Quien la coloca es
   ts/ui/tabs.ts; aquí va cómo se ve.

   El carril es TAN ANCHO COMO MIDE (`fit-content`): estirado, cruzaba la página
   con tres pestañas amontonadas a un lado. Y sigue siendo SU PROPIO envoltorio
   de desbordamiento (MF-5, chore 90.112): con cuatro pestañas y contador el
   expediente de una persona mide más que un teléfono de 360 px, y se desplaza en
   horizontal DENTRO de sí mismo en vez de ensanchar la página. No envuelve en dos
   renglones: el carril partido serían dos mandos. */
.bf-tabs {
  position: relative;
  display: flex;
  flex: none;
  align-self: flex-start;
  gap: 0.125rem;
  width: fit-content;
  max-width: 100%;
  box-sizing: border-box;
  padding: 0.25rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-bg) 45%, transparent);
  overflow-x: auto;
  /* Explícito: al pedir desplazamiento en un eje el navegador pone el otro en
     `auto`, y cualquier medio píxel de más sacaría una barra vertical fantasma. */
  overflow-y: hidden;
}

.bf-tabs a {
  /* Por delante de la pastilla, que va en el mismo carril. */
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.75rem;
  border-radius: 999px;
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
  text-decoration: none;
  /* Una pestaña no se encoge ni se parte: su rótulo es el nombre de la relación. */
  flex: none;
  white-space: nowrap;
  transition:
    color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-tabs a:hover {
  text-decoration: none;
}

/* El hover toca la superficie (S-5), y no en la puesta ni en la apagada. */
.bf-tabs a[href]:hover:not(.on):not([aria-current="page"]) {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

/* Cada pestaña lleva su icono delante del nombre (chore 90.430): se reconoce sin leerla. La puesta, con la tinta de
   marca, como el destino activo del lateral. */
.bf-tabs :is(a, button) > svg {
  flex: none;
  width: 15px;
  height: 15px;
}

.bf-tabs :is(a.on, a[aria-current="page"], button.on) > svg {
  color: var(--bf-selected-ink);
}

/* Pestañas que CONMUTAN en la página en vez de navegar (chore 90.430): botones con la forma de los enlaces; la
   pastilla la mueve ts/ui/tabs.ts (wireToggleTabs). */
.bf-tabs > button {
  position: relative;
  z-index: 1;
  display: flex;
  flex: none;
  align-items: center;
  gap: 0.375rem;
  padding: 0.375rem 0.75rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  font: inherit;
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-tabs > button:hover:not(.on) {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-tabs > button.on {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-tabs > button:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-tabs a .n {
  font-size: 11px;
  color: var(--bf-dim);
  font-variant-numeric: tabular-nums;
}

/* La puesta sube tinta y peso. El FONDO lo pone la pastilla que se desliza, no
   la pestaña: si lo pusieran las dos, al cambiar se encenderían dos superficies a
   la vez y se vería un parpadeo, no un viaje. */
.bf-tabs a.on,
.bf-tabs a[aria-current="page"] {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

/* LA PASTILLA. La coloca ts/ui/tabs.ts por `transform` y `width` —las dos que se
   interpolan—, medida contra el carril y no contra la ventana, así que se
   desplaza con él cuando el carril se desborda. El tinte de marca es mínimo (6 %)
   a propósito: tiene que decir «estás en esta», no «pulsa aquí», y más tinte la
   hacía pesar como el botón de acción de la misma pantalla. */
.bf-tabs-ink {
  position: absolute;
  top: 0.2rem;
  bottom: 0.2rem;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-accent) 6%, var(--bf-surface-bg));
  box-shadow: var(--bf-pill-shadow);
  pointer-events: none;
  transition:
    transform var(--bf-dur-glide) var(--bf-ease-out),
    width var(--bf-dur-glide) var(--bf-ease-out);
}

/* SIN PASTILLA —el marcado del servidor antes de que llegue el script, o sin
   script— el fondo vuelve a la puesta: elegida sin fondo no se distinguiría de
   las demás. */
.bf-tabs:not(:has(> .bf-tabs-ink)) :is(a.on, a[aria-current="page"]) {
  background: color-mix(in srgb, var(--bf-accent) 6%, var(--bf-surface-bg));
  box-shadow: var(--bf-pill-shadow);
}

/* La pestaña APAGADA del alta (F-2, chore 90.076): un registro que no existe no
   tiene relaciones, así que sus pestañas se ven —para que se sepa qué va a haber—
   pero no llevan a ninguna parte. Sin `href` no hay foco ni pulsación; el gris y
   el cursor lo dicen también a la vista. */
.bf-tabs a.off {
  color: var(--bf-dim);
  cursor: not-allowed;
}

.bf-scope {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 12.5px;
}

/* El pie de la auditoría es el pie de una tabla (chore 90.394): mismo relleno y filete. */
.bf-cursor {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0.75rem;
  border-top: 1px solid var(--bf-border);
  font-size: var(--bf-legend-font-size);
  color: var(--bf-muted);
}

/* Velo + cajón lateral + modal. El pie del CAJÓN va sobre relleno hundido porque
   la pieza entera flota sobre el velo: ahí sí hay superficie alrededor (M-5). El
   del diálogo ya no (chore 90.258, más abajo).

   EL VELO FUNDE (chore 90.258): entra en los mismos 200 ms que el diálogo y a la
   vez, para que la página se apague mientras llega la pregunta y no antes. Es una
   ANIMACIÓN y no una transición porque la superposición se enseña quitándole
   `hidden`: pasa de no pintarse a pintarse, y desde ahí no hay transición posible.
   Una animación, en cambio, arranca cada vez que la pieza empieza a pintarse: al
   abrirla su módulo, al crearla la confirmación y al llegar ya abierta del
   servidor. Con el movimiento apagado, la duración es de un milisegundo. */
/* El velo de detrás de un modal o un cajón, con el desenfoque del de Hugin (`.modal::backdrop`, blur de 2 px; chore
   90.453): la página se aparta sin dejar de leerse. Si se pide menos transparencia, sólo el tinte. */
.bf-scrim {
  position: absolute;
  inset: 0;
  background: var(--bf-scrim);
  -webkit-backdrop-filter: blur(2px);
  backdrop-filter: blur(2px);
  animation: bf-fade-in var(--bf-dur-base) var(--bf-ease-out);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-scrim {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

@keyframes bf-fade-in {
  from {
    opacity: 0;
  }
}

.bf-drawer {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 540px;
  background: var(--bf-surface-bg);
  border-left: 1px solid var(--bf-border);
  display: flex;
  flex-direction: column;
}

.bf-drawer > header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--bf-border);
  display: flex;
  align-items: flex-start;
  gap: 12px;
}

/* El título de un cajón es un dato de fuera —en la ficha del asiento es la CLAVE
   de la acción, `bifrost.admin.roles.permission.revoked`, una sola palabra de 315 px
   que ningún salto de línea natural parte—. Sin poder encogerse ni partirse empujaba
   al botón de cerrar fuera de la pantalla: medido a 360 px, «Cerrar» quedaba en
   x = 359, y como en compacto el cajón ocupa el ancho entero tampoco había velo que
   pulsar detrás. Es decir, la ficha solo se cerraba con el botón «atrás». */
.bf-drawer > header > :first-child {
  min-width: 0;
}

.bf-drawer > header h3 {
  font-size: 16px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.bf-drawer > header p {
  font-size: var(--bf-legend-font-size);
  color: var(--bf-dim);
  margin-top: 3px;
}

.bf-drawer .in {
  padding: 18px 20px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  overflow: hidden;
}

.bf-drawer footer {
  margin-top: auto;
  padding: 14px 20px;
  border-top: 1px solid var(--bf-border-strong);
  display: flex;
  gap: 9px;
  background: var(--bf-panel-bg);
  /* Envuelve (chore 90.073 · MF-5). Con TRES acciones —retirar, ir a la ficha y
     cerrar— a 360 px el tercer botón se salía del cajón, que recorta: quedaba
     fuera de alcance, y sin JavaScript «Cerrar» es la única salida que hay. */
  flex-wrap: wrap;
}

.bf-modal-wrap {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── EL DIÁLOGO ES MACIZO (chore 90.258 · contrato §3 · S-4) ───────────────────
   Todo lo demás que se abre encima —menú, aviso, pista— es cristal porque flota
   SOBRE algo que se sigue usando: se ve de dónde ha salido y qué tapa. El diálogo
   es lo contrario, y por eso es la superficie ELEVADA, opaca, con la sombra del
   diálogo (--bf-shadow, que desde la 90.251 ya no vale `none`). Conserva el filete
   fino del sistema de origen: en oscuro, la superficie elevada sobre el velo se
   funde con lo de detrás sin él. El radio es el de diálogo, más cerrado que el del
   cristal: una pregunta no es una lámina que flota.

   ENTRA en 200 ms con la curva de salida —sube tres cuartos de rem y crece desde el
   98 %— mientras el velo funde. Un diálogo que aparece de un fotograma para otro no
   dice de dónde ha salido. Es animación y no transición por lo mismo que el velo.
   NO SALE animado: se cierra con `hidden` —y sin JavaScript, siguiendo un enlace—,
   y cuando se pulsa «Cancelar» ya ha dicho lo que tenía que decir.

   Tampoco pasa a <dialog> nativo. Las superposiciones del servidor se abren por la
   dirección y se cierran con un enlace, sin JavaScript; lo que el nativo trae de
   serie —el foco atrapado, Escape, devolver el foco— lo pone ts/ui/overlay.ts con
   el ayudante de ts/ui/focus.ts. F-3 no cambia: confirma una acción puntual y no
   edita campos. */
/* CRISTAL DESDE LAS CHORES 90.746 Y 90.732 (petición del usuario): el modal se viste como las ventanas del modo
   sencillo del editor de paneles de Hugin (.editor-pop / .editor-add de editor-inspector.css): su velo
   (--bf-modal-bg: en oscuro #15191C al 88 %, en claro el de los desplegables), su desenfoque de 22 px, el radio del
   cristal (1,1 rem), SIN filete —el canto es el bisel fino de la casa, --bf-lg-edge— y SIN sombra (nada de sombras
   en reposo). Sin cristal, macizo (--bf-modal-solid, por las tres vías de tokens.css). */
.bf-modal {
  width: 520px;
  max-width: calc(100% - 24px); /* MF-5: a 360 el modal cede antes que desbordar */
  background: var(--bf-modal-bg);
  -webkit-backdrop-filter: var(--bf-modal-filter);
  backdrop-filter: var(--bf-modal-filter);
  border: 0;
  border-radius: var(--bf-glass-radius);
  box-shadow: var(--bf-lg-edge);
  overflow: hidden;
  position: relative;
  animation: bf-modal-in var(--bf-dur-base) var(--bf-ease-out);
}

@keyframes bf-modal-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem) scale(0.98);
  }
}

/* Cabecera y pie, los del sistema de origen: el título en el cuerpo de texto y en
   600 —a 16 px pesaba más que la pregunta que viene debajo—, y el pie en la MISMA
   superficie con un filete encima. Hundido, como el del cajón, partía el diálogo en
   dos materiales, y un diálogo macizo es una sola pieza. Los márgenes, en rem. */
.bf-modal > header {
  padding: 0.75rem 1.25rem;
  border-bottom: 1px solid var(--bf-border);
}

.bf-modal > header h3 {
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-modal .in {
  padding: 1rem 1.25rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

/* El pie, el de .editor-pop-foot de Hugin (chore 90.732): 0,5 × 0,625 rem y el pelo por encima, sin fondo. La
   cabecera ya medía lo mismo que la suya (2,75 rem con su pelo debajo) y no cambia. */
.bf-modal footer {
  padding: 0.5rem 0.625rem;
  border-top: 1px solid var(--bf-border);
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  justify-content: flex-end;
}

/* ── VARIAS SALIDAS (chore 90.258 · contrato §3) ───────────────────────────────
   Cuando lo que hay son dos o tres CAMINOS y no un sí o un no, cada uno es una
   FILA de ancho completo con su rótulo y, debajo, lo que pasa si se elige: se leen
   de arriba abajo y se elige leyendo. Tres botones en el pie obligan a leerlos de
   derecha a izquierda para averiguar cuál es cuál, y no dejan sitio para decir qué
   se pierde con cada uno. Las pinta confirmChoice (ts/ui/confirm.ts) dentro de un
   .bf-modal, en orden de recomendación; la salida sigue siendo «Cancelar», en el pie.

   La fila es un MANDO: filete de mando (M-12) sobre un velo, y el apuntado toca la
   superficie, no el canto (S-5). La recomendada lleva el canto teñido de la
   llamada a la acción —el canto y no la fila: teñida entera, una lista de tres
   caminos sería un botón grande con dos notas—, y la que destruye escribe su
   rótulo en rojo, porque se elige leyendo y nunca por inercia. */
.bf-choices {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.bf-choice {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
  width: 100%;
  padding: 0.625rem 0.75rem;
  border: 1px solid var(--bf-field-line);
  border-radius: var(--bf-radius-modal);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 55%, transparent);
  color: var(--bf-surface-fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    border-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-choice:hover {
  background-color: var(--bf-hover);
}

.bf-choice-label {
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-choice-hint {
  font-size: var(--bf-text-label);
  line-height: 1.35;
  color: var(--bf-muted);
}

.bf-choice[data-tone="primary"] {
  border-color: color-mix(in srgb, var(--bf-cta) 40%, transparent);
}

.bf-choice[data-tone="danger"] .bf-choice-label {
  color: var(--bf-danger-text);
}

/* ── EL AVISO EMERGENTE (chore 90.258 · contrato §3 · S-6 y S-12) ─────────────
   Hasta aquí el único `.bf-toast` era el rótulo del hito del visor de
   reproducción, pintado sobre pizarra y centrado. Pasa a ser la pieza de chasis
   del sistema de origen: CRISTAL (la receta de .bf-glass, por su lista de
   selectores), abajo a la derecha de la ventana, con una medalla que dice el tono
   antes de leer la frase y una barra al pie que se vacía mientras le queda tiempo.
   Lo pinta showToast (ts/ui/toast.ts); el rótulo del hito toma de aquí el
   material, la medalla y la entrada, y su sitio lo pone él (§9).

   DOS TONOS, y ninguno es rojo (S-6): «hecho» y «hecho, pero mira». Un fallo no se
   cuenta en algo que se va solo; se queda en un aviso fijo (.bf-msg.err) hasta que
   alguien lo lea. Por eso aquí no hay regla para un tercer tono: un tono que el
   catálogo no pinta no existe.

   No roba el foco ni se come el puntero cuando no está: al irse, el módulo le pone
   `hidden` en cuanto termina de fundirse, y con eso sale también del árbol de
   accesibilidad —un «guardado» de hace un minuto no tiene que seguir leyéndose—.
   Los 60 de capa lo dejan por encima de las superposiciones (50) y por debajo de la
   pista (100). */
.bf-toast {
  position: fixed;
  z-index: 60;
  right: 1rem;
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  overflow: hidden;
  min-width: min(15rem, calc(100vw - 2rem));
  max-width: min(26rem, calc(100vw - 2rem));
  padding: 0.625rem 0.75rem;
  /* El velo y el desenfoque de la CABECERA del shell (chore 90.713), no los del flotante; el canto, el del
     centro de control (--bf-glass-bevel, en la regla común de arriba). */
  background: var(--bf-head-glass);
  -webkit-backdrop-filter: var(--bf-head-filter);
  backdrop-filter: var(--bf-head-filter);
  font-size: var(--bf-text-ui);
  /* El renglón del cuerpo de Hugin (1,5): lo heredan la medalla, el aspa y todo lo
     que no fija el suyo (chore 90.485). */
  line-height: 1.5;
  /* Los tiempos de Hugin: entra en 260 ms con la curva de salida y se va en 180 ms
     con `ease`. Salen del token base para que el movimiento apagado los deje en nada. */
  transition:
    opacity calc(var(--bf-dur-base) * 0.9) ease,
    transform calc(var(--bf-dur-base) * 0.9) ease;
}

/* El hueco del aspa, que va en la esquina y fuera del flujo. */
.bf-toast:has(> .bf-toast-close) {
  padding-right: 2rem;
}

/* LA ENTRADA es una animación que el módulo REINICIA a mano, no la transición de
   una clase: el aviso se crea en la misma tarea en la que se enciende —no hay
   estado de partida y el navegador se salta la transición— y dos avisos seguidos
   comparten nodo, así que el segundo ni siquiera cambiaría de clase. La SALIDA sí
   es transición: el aviso ya está pintado y tiene de dónde partir. */
.bf-toast.is-in {
  animation: bf-toast-in calc(var(--bf-dur-base) * 1.3) var(--bf-ease-out);
}

@keyframes bf-toast-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem) scale(0.96);
  }
}

.bf-toast.is-out {
  opacity: 0;
  transform: translateY(0.5rem);
  pointer-events: none;
}

/* LA MEDALLA: el tono en un disco teñido, a la altura de la primera línea. En una
   esquina de la pantalla un glifo suelto no llega a decir el tono antes de que se
   lea la frase, que es justo para lo que está. El trazo es una máscara pintada con
   `currentColor` —el visto de la casilla para «hecho», el «ojo» para «mira»—, así
   que el color del tono lo pone una sola propiedad. Sin tono, la medalla es neutra:
   el rótulo del hito la tiñe él. */
.bf-toast-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.125rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 16%, transparent);
  color: var(--bf-muted);
}

.bf-toast-mark::after {
  content: "";
  width: 1rem;
  height: 1rem;
  background-color: currentColor;
  -webkit-mask: var(--bf-done-mark) center / contain no-repeat;
  mask: var(--bf-done-mark) center / contain no-repeat;
}

.bf-toast[data-tone="ok"] .bf-toast-mark {
  background-color: color-mix(in srgb, var(--bf-success) 18%, transparent);
  color: var(--bf-success-text);
}

.bf-toast[data-tone="warn"] .bf-toast-mark {
  background-color: color-mix(in srgb, var(--bf-warning) 18%, transparent);
  color: var(--bf-warning-text);
}

.bf-toast[data-tone="warn"] .bf-toast-mark::after {
  -webkit-mask-image: var(--bf-warn-mark);
  mask-image: var(--bf-warn-mark);
}

/* QUÉ ha pasado y, debajo y en apagado, DÓNDE o sobre qué. Dos renglones porque son
   dos preguntas: la primera se lee de reojo y la segunda sólo si la primera
   sorprende. */
.bf-toast-body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
  padding-top: 0.125rem;
}

.bf-toast-body > strong {
  font-weight: 600;
  line-height: 1.3;
}

.bf-toast-detail {
  font-size: var(--bf-text-label);
  line-height: 1.35;
  color: var(--bf-muted);
}

/* EL ASPA, en la esquina y sin superficie hasta que se apunta: con el velo puesto
   siempre era la pieza más marcada del aviso y competía con lo que venía a decir. */
.bf-toast-close {
  position: absolute;
  top: 0.3rem;
  right: 0.3rem;
  display: grid;
  place-items: center;
  width: 1.3rem;
  height: 1.3rem;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-input);
  background-color: transparent;
  color: var(--bf-muted);
  font: inherit;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-toast-close::before {
  content: "";
  width: 0.7rem;
  height: 0.7rem;
  background-color: currentColor;
  -webkit-mask: var(--bf-close-mark) center / contain no-repeat;
  mask: var(--bf-close-mark) center / contain no-repeat;
}

.bf-toast-close:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

/* LA BARRA: lo que le queda, en una línea al pie. Un aviso que desaparece sin
   avisar deja la duda de si se leyó todo; con la barra, la desaparición está
   anunciada desde el principio. Corre con --bf-dur-toast (3,2 s) y se para cuando
   el aviso está apuntado o enfocado —el módulo pone [data-held]—, porque ahí el
   tiempo tampoco corre. Con el movimiento apagado el token vale un milisegundo, y
   una barra que se vacía de golpe dejaría de decir nada: el módulo lo detecta y le
   pone [data-still], que la deja llena y quieta. */
.bf-toast-bar {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 2px;
  transform-origin: left;
  background-color: color-mix(in srgb, var(--bf-muted) 45%, transparent);
  animation: bf-toast-drain var(--bf-dur-toast) linear forwards;
}

.bf-toast[data-tone="ok"] .bf-toast-bar {
  background-color: color-mix(in srgb, var(--bf-success) 55%, transparent);
}

.bf-toast[data-tone="warn"] .bf-toast-bar {
  background-color: color-mix(in srgb, var(--bf-warning) 55%, transparent);
}

.bf-toast[data-held] .bf-toast-bar {
  animation-play-state: paused;
}

.bf-toast-bar[data-still] {
  animation: none;
}

@keyframes bf-toast-drain {
  from {
    transform: scaleX(1);
  }

  to {
    transform: scaleX(0);
  }
}

/* ── LA PISTA (chore 90.258 · contrato §3) ─────────────────────────────────────
   Sustituye al `title` nativo, que se pinta con el cromo del navegador —ni tema,
   ni tipografía— y tarda un segundo en salir. Es cristal (la receta de .bf-glass,
   por su lista) con el radio del mando: una pista es una nota al margen, no una
   lámina. La pinta ts/ui/tip.ts en UN nodo colgado del <body> y colocado con
   coordenadas de ventana —un ::after dentro del elemento lo recortaría cualquier
   ascendiente con `overflow`, y las celdas de las tablas lo tienen—; aquí va sólo
   el aspecto y la entrada.

   Entra desde el lado del que sale: debajo del elemento, subiendo; encima,
   bajando. La opacidad en --bf-dur-fast y el recorrido en --bf-dur-base, como en el
   sistema de origen. Al irse se queda `visibility: hidden` en cuanto termina de
   fundirse, para que no la alcance nada. Nunca recibe el puntero: si lo recibiera,
   al pasar por encima de ella se cerraría y volvería a abrirse sin fin. */
.bf-tip {
  position: fixed;
  z-index: 100;
  top: 0;
  left: 0;
  width: max-content;
  max-width: min(22rem, calc(100vw - 2rem));
  padding: 0.25rem 0.5rem;
  border-radius: var(--bf-radius-btn);
  /* La del carril del shell (chore 90.973): su cristal, su tinta, su caída y su bisel; sin canto. */
  background-color: var(--bf-tip-glass);
  -webkit-backdrop-filter: var(--bf-tip-filter);
  backdrop-filter: var(--bf-tip-filter);
  box-shadow: var(--bf-tip-drop), var(--bf-tip-bevel);
  color: var(--bf-tip-fg);
  font-size: var(--bf-text-mini);
  font-weight: 400;
  line-height: 1.4;
  overflow-wrap: anywhere;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition:
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    transform var(--bf-dur-base) var(--bf-ease-out),
    visibility 0s linear var(--bf-dur-fast);
}

.bf-tip[data-side="bottom"] {
  transform: translateY(-0.3rem);
}

.bf-tip[data-side="top"] {
  transform: translateY(0.3rem);
}

.bf-tip[data-show="true"] {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition-delay: 0s;
}

/* ── EL CALENDARIO (chore 90.260 · contrato §3 · S-4 y S-12) ──────────────────
   Sustituye al cuadro de fecha del navegador, que se ve distinto en cada sistema,
   no sigue el tema, sale en el idioma del SISTEMA y empieza la semana donde
   quiere. Es el de Hugin (`.dp`) con los tokens de aquí: cristal (la receta de
   .bf-glass, por su lista), semana en LUNES, seis semanas siempre —si la rejilla
   cambiara de alto al pasar de mes, el cuadro saltaría bajo el dedo— y tres
   niveles (días, meses, años) que miden LO MISMO, por la misma razón. Lo pinta
   ts/ui/datepick.ts en UN nodo de la capa superior (`popover`): ningún cajón ni
   diálogo con `overflow` lo recorta, y queda por encima de los dos. Aquí van el
   aspecto y la entrada, con los tiempos de los desplegables (T9).

   El CAMPO es `.bf-date`: el <input type="date"> de siempre y, a su lado, el botón
   del calendario, que AdminDateTagHelper pinta escondido y el módulo enseña. Sólo
   entonces (`data-dp="on"`) se va el indicador del navegador —abre SU cuadro, que
   es justo lo que se sustituye— y el campo deja sitio al botón. Sin JavaScript no
   se toca nada: el control nativo es el respaldo. */
.bf-date {
  position: relative;
  display: block;
}

.bf-date[data-dp="on"] > input[type="date"] {
  padding-right: 2rem;
}

.bf-date[data-dp="on"] > input[type="date"]::-webkit-calendar-picker-indicator {
  display: none;
}

.bf-date-open {
  position: absolute;
  top: 50%;
  right: 0.25rem;
  transform: translateY(-50%);
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-input);
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
  transition:
    color var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-date-open > svg {
  width: 0.95rem;
  height: 0.95rem;
}

.bf-date-open:hover,
.bf-date-open[aria-expanded="true"] {
  color: var(--bf-surface-fg);
  background-color: var(--bf-hover);
}

/* Con el campo apagado (el alta de Usuarios apaga los del modo que no toca), el
   botón tampoco abre: se apaga con él y deja de invitar. */
.bf-date-open:disabled {
  color: var(--bf-dim);
  background-color: transparent;
  cursor: not-allowed;
}

/* El cuadro. La capa superior trae su propio aspecto de fábrica (centrado, con
   borde y fondo del sistema): se deshace aquí y lo pone el cristal. Entra desde
   el campo, un pelo más arriba y más pequeño, y al irse se funde igual; con el
   movimiento reducido los dos tiempos ya valen 1 ms desde tokens.css. */
.bf-dp {
  position: fixed;
  inset: auto;
  margin: 0;
  width: max-content;
  max-width: calc(100vw - 1rem);
  max-height: calc(100vh - 1rem);
  max-height: calc(100dvh - 1rem);
  overflow: auto;
  padding: 0.375rem;
  border: 0;
  font-size: var(--bf-text-label);
  display: none;
  opacity: 0;
  transform: translateY(-0.35rem) scale(0.97);
  transform-origin: top left;
  transition:
    opacity var(--bf-dur-pop) var(--bf-ease-out),
    transform var(--bf-dur-pop-move) var(--bf-ease-pop),
    display var(--bf-dur-pop-move) allow-discrete,
    overlay var(--bf-dur-pop-move) allow-discrete;
}

.bf-dp[data-side="top"] {
  transform-origin: bottom left;
}

.bf-dp:popover-open {
  display: block;
  opacity: 1;
  transform: none;
}

@starting-style {
  .bf-dp:popover-open {
    opacity: 0;
    transform: translateY(-0.35rem) scale(0.97);
  }
}

.bf-dp-head {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  margin-bottom: 0.25rem;
}

.bf-dp-nav,
.bf-dp-title,
.bf-dp-today {
  margin: 0;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition:
    color var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-dp-nav {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border-radius: 999px;
  color: var(--bf-muted);
}

.bf-dp-nav > svg {
  width: 0.85rem;
  height: 0.85rem;
}

/* EL TÍTULO ES UN BOTÓN: sube de nivel (días → meses → años). Tiene que parecerlo
   sin dejar de ser el rótulo que dice qué se está mirando, así que en reposo no
   lleva ni marco ni fondo, y el aviso lo da al apuntarlo. */
.bf-dp-title {
  flex: 1;
  min-width: 0;
  height: 1.75rem;
  padding: 0 0.25rem;
  border-radius: var(--bf-radius-btn);
  text-align: center;
  font-size: var(--bf-text-ui);
  font-weight: 600;
}

.bf-dp-nav:hover,
.bf-dp-title:hover,
.bf-dp-today:hover {
  color: var(--bf-surface-fg);
  background-color: var(--bf-hover);
}

/* Lo que no lleva a ninguna parte —el título en la década, la flecha más allá de
   la fecha mínima o máxima del campo— se queda, pero deja de invitar. */
.bf-dp-nav[aria-disabled="true"],
.bf-dp-title[aria-disabled="true"],
.bf-dp-today:disabled {
  background-color: transparent;
  cursor: default;
}

.bf-dp-nav[aria-disabled="true"],
.bf-dp-today:disabled {
  color: var(--bf-dim);
  opacity: 0.4;
}

.bf-dp-title[aria-disabled="true"] {
  color: inherit;
}

/* La rejilla: siete columnas de 2 rem, la tira de iniciales y seis semanas. Los
   niveles de meses y de años reparten ese MISMO sitio en cuatro filas de tres
   —doce meses en siete columnas no son nada—, para que el cuadro no cambie de
   tamaño al subir de nivel: se abre pegado al campo, y un salto de ancho lo
   desplazaría bajo el dedo. */
.bf-dp-grid {
  border-collapse: collapse;
  border-spacing: 0;
  table-layout: fixed;
}

.bf-dp-grid th,
.bf-dp-grid td {
  width: 2rem;
  padding: 0;
  text-align: center;
}

.bf-dp-grid th {
  height: 1.25rem;
  font-size: var(--bf-text-micro);
  font-weight: 600;
  letter-spacing: 0.05em;
  color: var(--bf-muted);
}

.bf-dp-grid td {
  height: 1.875rem;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
}

/* La cara de la casilla. Redonda del todo en los días: en una rejilla de 42, los
   cuadrados con esquina se leen como una tabla y los círculos como días. */
.bf-dp-grid td > span {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 999px;
  vertical-align: middle;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-dp[data-level="month"] .bf-dp-grid td,
.bf-dp[data-level="year"] .bf-dp-grid td {
  width: calc(14rem / 3);
  height: calc((1.25rem + 6 * 1.875rem) / 4);
}

/* Un mes o un año es una palabra o cuatro cifras: rectángulo redondeado, que un
   círculo con «sept» dentro se lee como una ficha y no como una opción. */
.bf-dp[data-level="month"] .bf-dp-grid td > span,
.bf-dp[data-level="year"] .bf-dp-grid td > span {
  width: calc(14rem / 3 - 0.375rem);
  height: calc((1.25rem + 6 * 1.875rem) / 4 - 0.375rem);
  border-radius: var(--bf-radius-btn);
}

.bf-dp-grid td:hover > span {
  background-color: var(--bf-hover);
}

/* Lo de fuera —los días del mes de al lado, los años de la década de al lado— NO se
   esconde, que la rejilla perdería la forma de la semana, pero pesa menos: es
   contexto, no la opción que se está mirando. */
.bf-dp-grid td[data-out] > span {
  color: color-mix(in srgb, var(--bf-muted) 65%, transparent);
}

/* HOY lleva un punto debajo y no un relleno: el relleno es lo ELEGIDO, y dos
   rellenos distintos en la misma rejilla se leen como dos selecciones. */
.bf-dp-grid td[aria-current] > span {
  font-weight: 700;
}

.bf-dp-grid td[aria-current] > span::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 0.15rem;
  width: 3px;
  height: 3px;
  border-radius: 999px;
  background-color: var(--bf-cta);
  transform: translateX(-50%);
}

/* Lo elegido va en el naranja de la acción (S-8: un ajuste no es un estado). */
.bf-dp-grid td[aria-selected="true"] > span {
  background-color: var(--bf-cta);
  color: var(--bf-on-cta);
  font-weight: 600;
}

.bf-dp-grid td[aria-selected="true"] > span::after {
  background-color: var(--bf-on-cta);
}

/* Fuera de la fecha mínima o máxima del campo: se ve, pero no se elige. */
.bf-dp-grid td[aria-disabled="true"] {
  cursor: not-allowed;
}

.bf-dp-grid td[aria-disabled="true"] > span {
  background-color: transparent;
  color: var(--bf-dim);
  opacity: 0.4;
}

/* EL FOCO VA EN LA CARA, no en la casilla: el aro alrededor de un área de 2 rem (o
   de 44 px en táctil) se despegaría del círculo que se está mirando. */
.bf-dp-grid td:focus-visible {
  outline: none;
  box-shadow: none;
}

.bf-dp-grid td:focus-visible > span {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
  box-shadow: 0 0 0 1px var(--bf-surface-fg);
}

.bf-dp-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.375rem;
  padding-top: 0.375rem;
  border-top: 1px solid var(--bf-border);
}

.bf-dp-today {
  height: 1.75rem;
  padding: 0 0.625rem;
  border-radius: var(--bf-radius-btn);
  color: var(--bf-muted);
}

/* Lista de definición: el detalle de una ficha.
   El carril del valor va en `minmax(0, 1fr)` y no en `1fr` (MF-5): un `1fr` a secas
   no baja del contenido MÍNIMO de su celda, y aquí los valores son identificadores
   —el UUID del asiento mide 272 px en monoespaciada y no tiene por dónde partirse—,
   así que la lista entera se salía de su ficha y el cajón la recortaba sin scroll.
   Con el tope de encogido y el corte por cualquier letra, el identificador baja de
   renglón en vez de desaparecer. A ancho completo no cambia nada: ahí sobra sitio y
   ningún valor llega a partirse. */
.bf-dl {
  display: grid;
  grid-template-columns: 116px minmax(0, 1fr);
  gap: 8px 14px;
  font-size: var(--bf-table-row-font-size);
  margin: 0;
}

.bf-dl dt {
  color: var(--bf-dim);
  font-size: var(--bf-legend-font-size);
}

.bf-dl dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Un valor LARGO de varias líneas dentro de la lista (chore 90.112: el user agent completo del cajón
   de un dispositivo, que es justo lo que la fila resume y el cajón existe para enseñar). Baja de
   cuerpo y abre el interlineado, como lo dibuja la maqueta: sin esto son tres renglones apretados. */
.bf-dl dd.long {
  font-size: var(--bf-legend-font-size);
  line-height: 1.5;
}

/* Diferencia antes/después (auditoría). */
.bf-diff {
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-m);
  overflow: hidden;
  font-family: var(--bf-font-mono);
  font-size: var(--bf-legend-font-size);
}

.bf-diff header {
  display: grid;
  grid-template-columns: 1fr 1fr;
  background: var(--bf-panel-bg);
  border-bottom: 1px solid var(--bf-border);
}

.bf-diff header span {
  padding: 7px 12px;
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bf-dim);
  font-family: var(--bf-font-family);
}

.bf-diff header span + span {
  border-left: 1px solid var(--bf-border);
}

.bf-diff .row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  border-bottom: 1px solid var(--bf-border-soft);
}

.bf-diff .row:last-child {
  border-bottom: 0;
}

.bf-diff .row > i {
  font-style: normal;
  padding: 6px 12px;
  display: block;
  white-space: pre;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bf-diff .row > i + i {
  border-left: 1px solid var(--bf-border-soft);
}

.bf-diff .row.del > i:first-child {
  background: var(--bf-danger-soft);
  color: var(--bf-danger-text);
}

.bf-diff .row.add > i:last-child {
  background: var(--bf-success-soft);
  color: var(--bf-success-text);
}

.bf-diff .row.same > i {
  color: var(--bf-muted);
}

/* ═══ 6 · Alternativas a la tabla (M-11) ═════════════════════════════════════
   Para las pantallas donde una rejilla de filas iguales esconde la información
   en vez de mostrarla. La tabla no desaparece: queda tras un conmutador. */

.bf-grid3,
.bf-grid4 {
  display: grid;
  gap: 14px;
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Mobile-first: la cuenta de columnas es la de la maqueta a ancho completo;
   en el teléfono las tarjetas apilan y en medio van a dos. Con minmax(0, 1fr)
   para que una clave larga no ensanche su columna (MF-5). */
.bf-grid3,
.bf-grid4 {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 620px) {
  .bf-grid3,
  .bf-grid4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 900px) {
  .bf-grid3 {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }

  .bf-grid4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* ── La tendencia corta ─────────────────────────────────────────────────────── */

/* Es pieza PROPIA y no un detalle de la tarjeta (chore 90.076): la ficha de la instancia enseña la misma serie en su
   pestaña Salud y el inicio la usa en sus cartas. */
.spark {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 1.375rem;
}

.spark i {
  flex: 1;
  background: color-mix(in srgb, var(--bf-success) 55%, transparent);
  border-radius: 1px;
  min-height: 2px;
}

/* Una franja con fallos y ningún acierto se marca por sí sola: en una instancia que hoy responde, el bache de esta
   madrugada sigue siendo información. El hueco se queda fuera: una hora que nadie midió no se tiñe por el estado de
   hoy (chore 90.217). */
.spark i.bad {
  background: color-mix(in srgb, var(--bf-danger) 55%, transparent);
}

/* El tercer estado: la hora SIN SONDEO. Apagado = «todavía no sé nada de esto». */
.spark i.none {
  background: color-mix(in srgb, var(--bf-off) 30%, transparent);
}

/* La escala, al pie: doce barras no dicen por sí solas qué ventana cubren. */
.spark-scale {
  margin: 0;
  font-size: var(--bf-text-mini);
  line-height: 1.3;
  color: var(--bf-dim);
}

/* ═══ LAS TARJETAS DE HUGIN (chores 90.456 y 90.457) ═══════════════════════════════════════════════════════════════
   La tarjeta de los paneles de Hugin (glass.css .card, gallery.css .card[data-open], .card-preview y .card-body) y la
   de procesos de Latanur, que la porta tal cual (wall.css .pcard). La usan Productos y Instancias:
     · VELO de tarjeta al 54 % SIN BORDE; lo que la dibuja es su canto de cristal y el bisel, encima de todo.
     · Al apuntar SUBE 2 px, se aclara al 62 % y enciende su sombra; al pulsar, vuelve abajo.
     · Arriba, una VISTA de 8/5 con un halo del TONO de la tarjeta y la baldosa del icono teñida de él (--hc-tone: el
       color del producto, o el estado de la instancia).
     · Abajo, la BANDA esmerilada que flota sobre el pie de la vista: nombre, una línea y el pie de cifras.
     · El «···» flota arriba a la derecha y sale al apuntar; abre HACIA ABAJO. Para que su menú no se recorte, la
       tarjeta no recorta: redondean la vista y la banda, y la tarjeta con el menú abierto sube por encima de las demás.
     · La tarjeta ENTERA abre (un enlace de fondo, fuera del tabulador; el del nombre es el de verdad).
   Entran en cascada como la rejilla de Hugin, rellenando sólo HACIA ATRÁS: una animación que se queda puesta haría de
   cada tarjeta raíz del desenfoque de su banda. */
.bf-hgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 1fr));
  gap: 0.75rem;
}

/* La entrada NO baja (chore 90.463): la de Hugin sube desde 0,9 rem más abajo, y esos 0,9 rem de la última fila
   alargaban medio segundo el panel que rueda —medido: 721 de 710 px—, así que al cargar asomaba una barra de
   desplazamiento que luego se iba aunque todo cupiera. Aquí se funde y crece desde su canto de arriba: nunca sale de
   su hueco. */
.bf-hgrid > * {
  transform-origin: 50% 0;
  animation: bf-hcard-in 520ms cubic-bezier(0.16, 1, 0.3, 1) backwards;
  animation-delay: calc(min(var(--i, 0), 12) * 55ms);
}

@keyframes bf-hcard-in {
  from {
    opacity: 0;
    transform: scale(0.965);
  }
}

.bf-hcard {
  --hc-tone: var(--bf-muted);
  --hc-ink: color-mix(in srgb, var(--hc-tone) 70%, var(--bf-surface-fg));
  --hc-band: 4.25rem;
  position: relative;
  display: block;
  min-width: 0;
  border: 0;
  border-radius: var(--bf-radius-modal);
  background-color: var(--bf-card-veil);
  overflow: visible;
  isolation: isolate;
  transition:
    transform 220ms var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    opacity var(--bf-dur-base) var(--bf-ease-out);
}

/* La sombra, en su capa: se enciende con opacidad, que es lo que se anima barato. */
.bf-hcard::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  box-shadow: 0 8px 18px -10px color-mix(in srgb, var(--bf-shadow-ink) 45%, transparent);
  opacity: 0;
  transition: opacity 220ms var(--bf-ease-out);
  pointer-events: none;
}

/* El canto de cristal y el bisel, ENCIMA de la vista y la banda. */
.bf-hcard::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--hc-edge, transparent), var(--bf-glass-bevel);
  pointer-events: none;
}

.bf-hcard:hover {
  transform: translateY(-2px);
  background-color: var(--bf-card-veil-hover);
}

.bf-hcard:hover::before {
  opacity: 1;
}

.bf-hcard:active {
  transform: translateY(0);
  transition-duration: 90ms;
}

/* La que no está en servicio (retirada, sin desplegar o inactiva) baja de intensidad; lo dice también por escrito. */
.bf-hcard.dim {
  opacity: 0.62;
}

.bf-hcard.dim:hover {
  opacity: 0.85;
}

.bf-hcard:has(> .bf-more[open]) {
  z-index: 6;
}

/* El enlace de fondo: la tarjeta entera abre la ficha. */
.bf-hcard > .hit {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
}

/* LA VISTA. */
.bf-hcard > .pv {
  position: relative;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 0.75rem;
  aspect-ratio: 8 / 5;
  padding: 0.375rem 0.375rem calc(var(--hc-band) + 0.375rem);
  border-radius: var(--bf-radius-modal) var(--bf-radius-modal) 0 0;
  overflow: hidden;
  isolation: isolate;
  pointer-events: none;
}

/* El halo del tono, en el ::before: el ::after de la vista es el brillo del relieve (chore 90.463). */
.bf-hcard > .pv::before {
  content: "";
  position: absolute;
  inset: 0 0 var(--hc-band);
  z-index: -1;
  pointer-events: none;
  /* De día, PLANO y tenue (chore 90.674): sobre el papel claro el halo radial del tono —sobre todo el gris de «sin
     sondear»— se leía como una mancha sucia. De noche, el halo de siempre (abajo). */
  background: color-mix(in srgb, var(--hc-tone) 4%, transparent);
  transition: background var(--bf-dur-base) var(--bf-ease-out);
}

:root[data-theme="dark"] .bf-hcard > .pv::before {
  background: radial-gradient(85% 78% at 50% 46%,
      color-mix(in srgb, var(--hc-tone) 12%, transparent),
      color-mix(in srgb, var(--hc-tone) 3%, transparent) 62%,
      transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-hcard > .pv::before {
    background: radial-gradient(85% 78% at 50% 46%,
        color-mix(in srgb, var(--hc-tone) 12%, transparent),
        color-mix(in srgb, var(--hc-tone) 3%, transparent) 62%,
        transparent);
  }
}

/* La baldosa: el tono al 20 % con su canto al 26 %, y el icono en la tinta del tono. Sin icono, las iniciales. */
.bf-hcard .tile,
.bf-htile {
  display: grid;
  place-items: center;
  flex: none;
  background: color-mix(in srgb, var(--hc-tone) 20%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hc-tone) 26%, transparent);
  color: color-mix(in srgb, var(--hc-tone) 70%, var(--bf-surface-fg));
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.bf-hcard .tile {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.85rem;
  font-size: var(--bf-text-body);
  transition: transform 700ms cubic-bezier(0.16, 0.7, 0.25, 1), background var(--bf-dur-base) var(--bf-ease-out);
}

.bf-hcard .tile svg {
  width: 1.35rem;
  height: 1.35rem;
}

.bf-hcard:hover .tile {
  transform: scale(1.04);
}

/* La marca de arriba a la izquierda (la clave del producto, el estado de la instancia). */
.bf-hcard > .pv > .chip,
.bf-hcard > .pv > .state {
  position: absolute;
  top: 0.55rem;
  left: 0.65rem;
  max-width: calc(100% - 3.5rem);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

/* EL «···» de Hugin (controls.css .card-menu-float): arriba a la derecha, al apuntar la tarjeta o al enfocarlo. */
.bf-hcard > .bf-more {
  position: absolute;
  top: 0.4rem;
  right: 0.4rem;
  z-index: 4;
  margin: 0;
  opacity: 0;
  transition: opacity var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-hcard:hover > .bf-more,
.bf-hcard > .bf-more:focus-within,
.bf-hcard > .bf-more[open] {
  opacity: 1;
}

@media (hover: none) {
  .bf-hcard > .bf-more {
    opacity: 1;
  }
}

.bf-hcard > .bf-more > summary,
.bf-htable td.acts .bf-more > summary {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border-radius: var(--bf-radius-btn);
  color: var(--bf-muted);
  letter-spacing: 0;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out), color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-hcard > .bf-more > summary {
  background-color: color-mix(in srgb, var(--bf-canvas) 82%, transparent);
}

.bf-hcard > .bf-more > summary > svg,
.bf-htable td.acts .bf-more > summary > svg {
  width: 0.95rem;
  height: 0.95rem;
}

.bf-hcard > .bf-more > summary:hover,
.bf-hcard > .bf-more[open] > summary,
.bf-htable td.acts .bf-more > summary:hover,
.bf-htable td.acts .bf-more[open] > summary {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

/* Abre HACIA ABAJO desde su esquina: arriba no hay pie que tapar. */
.bf-hcard > .bf-more .pop,
.bf-htable td.acts .bf-more .pop {
  top: calc(100% + 6px);
  bottom: auto;
  transform-origin: top right;
  animation-name: bf-pop-down;
}

@keyframes bf-pop-down {
  from {
    opacity: 0;
    transform: translate(0.15rem, -0.4rem) scale(0.96);
  }
}

/* ── EL ORDEN DE LAS TARJETAS, por arrastre (chore 90.980; ts/ui/reorder.ts) ─────────────────────────────────────────
   Instancias y Productos recolocan sus tarjetas como los paneles de Hugin y las sueltan en una carpeta del árbol (su
   destino, encendido, es «DESTINO DE UN ARRASTRE» del árbol de carpetas). Sólo con tokens del tema:
     · EL ASA es la del editor de menú (`.bf-menued .grip`): los seis puntos en el gris callado, sin canto, con el velo
       del apuntado y la mano de agarrar. Va arriba a la derecha, pegada al «···» y con su mismo cristal, y como él sale
       al apuntar o enfocar la tarjeta. Con puntero grueso no se pinta: el arrastre HTML5 no va con el dedo.
     · LA QUE VIAJA deja en su hueco una huella tenue con el trazo discontinuo del acento: es donde caerá.
     · Las demás se apartan con FLIP desde el módulo, que con «menos movimiento» no anima. */
.bf-hcard > .bf-cardgrip {
  position: absolute;
  top: 0.4rem;
  /* A la izquierda del «···»: su canto (0,4 rem), su ancho (1,6 rem) y un respiro. */
  right: 2.25rem;
  z-index: 4;
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: var(--bf-radius-s);
  background-color: color-mix(in srgb, var(--bf-canvas) 82%, transparent);
  color: var(--bf-dim);
  cursor: grab;
  user-select: none;
  opacity: 0;
  transition:
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-hcard > .bf-cardgrip > svg {
  width: 16px;
  height: 16px;
}

.bf-hcard:hover > .bf-cardgrip,
.bf-hcard:focus-within > .bf-cardgrip {
  opacity: 1;
}

.bf-hcard > .bf-cardgrip:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-hcard > .bf-cardgrip:active {
  cursor: grabbing;
}

/* La marca de arriba a la izquierda deja sitio también al asa, no sólo al «···». */
.bf-hgrid[data-reorder] > .bf-hcard > .pv > .chip,
.bf-hgrid[data-reorder] > .bf-hcard > .pv > .state {
  max-width: calc(100% - 5.25rem);
}

/* Mover una tarjeta la saca y la vuelve a meter, y eso reiniciaría su entrada en cascada: desde el primer arrastre,
   la rejilla ya no la tiene (había acabado hace rato). */
.bf-hgrid[data-ordering] > * {
  animation: none;
}

.bf-hgrid > .bf-hcard.is-dragging {
  opacity: 0.5;
  transform: scale(0.985);
  background-color: color-mix(in srgb, var(--bf-accent) 10%, var(--bf-card-veil));
  outline: 1px dashed color-mix(in srgb, var(--bf-accent) 55%, transparent);
  outline-offset: -1px;
}

.bf-hgrid > .bf-hcard.is-dragging::before {
  opacity: 0;
}

@media (pointer: coarse) {
  .bf-hcard > .bf-cardgrip {
    display: none;
  }
}

:root[data-reduce~="motion"] .bf-hcard > .bf-cardgrip {
  transition: none;
}

:root[data-reduce~="motion"] .bf-hgrid > .bf-hcard.is-dragging {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .bf-hcard > .bf-cardgrip {
    transition: none;
  }

  .bf-hgrid > .bf-hcard.is-dragging {
    transform: none;
  }
}

/* LA BANDA esmerilada, pegada al pie y flotando sobre la vista. */
.bf-hcard > .band {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.0625rem;
  min-height: var(--hc-band);
  padding: 0.375rem 0.75rem 0.45rem;
  border-top: 1px solid color-mix(in srgb, var(--bf-border) 70%, transparent);
  border-radius: 0 0 var(--bf-radius-modal) var(--bf-radius-modal);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 62%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  backdrop-filter: blur(12px) saturate(1.3);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-hcard > .band {
    background-color: color-mix(in srgb, var(--bf-surface-bg) 92%, transparent);
  }
}

.bf-hcard .nm {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--bf-text-body);
  font-weight: 600;
  line-height: 1.25;
  color: var(--bf-surface-fg);
  text-decoration: none;
}

/* El nombre es el enlace de verdad; su capa cubre la banda para que pulsarla también abra. */
.bf-hcard > .band > a.nm::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.bf-hcard > .band > a.nm:focus-visible {
  outline: none;
}

.bf-hcard:has(> .band > a.nm:focus-visible) {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-hcard .band > .sub {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--bf-text-label);
  line-height: 1.35;
  color: var(--bf-muted);
}

/* El pie: estado y cifras, a lo que dé la banda. Por encima de la capa del nombre, para que sus títulos se lean. */
.bf-hcard .ft {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.375rem;
  margin-top: 0.25rem;
  font-size: var(--bf-text-mini);
  line-height: 1.3;
  color: var(--bf-muted);
}

.bf-hcard .ft > * {
  position: relative;
  z-index: 1;
}

/* LAS CIFRAS (Hugin .badge): pastilla neutra con su glifo; la que se pasa del tope, en ámbar. */
.bf-hbadge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  min-width: 1rem;
  height: 1.125rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 18%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.bf-hbadge > svg {
  width: 0.7rem;
  height: 0.7rem;
  flex: none;
  opacity: 0.75;
}

/* LA QUE MIDE CONTRA UN TOPE (chore 90.456; la barra de la 90.139, dentro de la pastilla): se llena del tono de la
   tarjeta en proporción a lo contratado. */
.bf-hbadge.gauge {
  background-image: linear-gradient(90deg,
      color-mix(in srgb, var(--hc-tone, var(--bf-accent)) 34%, transparent) var(--p, 0%),
      transparent var(--p, 0%));
  color: var(--bf-surface-fg);
}

.bf-hbadge.over {
  background-image: none;
  background-color: var(--bf-warning-soft);
  color: var(--bf-warning-text);
}

/* EL ESTADO ESCRITO (Latanur .pstatus): su punto y su palabra, en la tinta del estado. */
.bf-hstatus {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
  font-weight: 500;
  color: var(--bf-muted);
}

.bf-hstatus > i {
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: currentColor;
}

.bf-hstatus.ok {
  color: var(--bf-success-text);
}

.bf-hstatus.off > i {
  background: color-mix(in srgb, var(--bf-muted) 55%, transparent);
}

/* La salud agregada del producto, en la vista, bajo su baldosa. */
.bf-hcard .bf-dotline {
  flex-wrap: wrap;
  justify-content: center;
  max-width: 80%;
  padding: 0;
}

/* ── Productos: el tono es el color del producto (el de su cuadro de siempre) ── */
.bf-prodcard > .pv > .chip {
  font-family: var(--bf-font-mono, ui-monospace, monospace);
  letter-spacing: 0.02em;
}

/* ── Instancias: el tono es su ESTADO, y cambia con el refresco (la clase la pone el módulo) ── */
.bf-inst.bf-hcard {
  --hc-tone: var(--bf-success);
}

.bf-inst.bf-hcard.warn {
  --hc-tone: var(--bf-warning);
}

.bf-inst.bf-hcard.bad {
  --hc-tone: var(--bf-danger);
}

.bf-inst.bf-hcard.off {
  --hc-tone: var(--bf-off);
}

/* La baldosa de la instancia es la de su producto: neutra, y el estado se lo dejan el halo, el LED y el texto. */
.bf-inst.bf-hcard .tile {
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 10%, transparent);
  color: var(--bf-surface-fg);
  /* MÁS GRANDE y en el CENTRO de la vista (chore 90.680): era la pieza que dice qué es, y a 2,75 rem se perdía. La
     vista centra su contenido con el hueco de la banda debajo; la baldosa se alinea a ese centro por sí sola. */
  width: 3.75rem;
  height: 3.75rem;
  border-radius: 1.1rem;
  align-self: center;
  justify-self: center;
  font-size: var(--bf-text-lead, 1.125rem);
}

.bf-inst.bf-hcard .tile svg {
  width: 1.85rem;
  height: 1.85rem;
}

/* La categoría en la banda (chore 90.647): con su glifo, tras el producto, y recortable como el resto de la línea. */
.bf-inst.bf-hcard .band > .sub > .cat {
  margin-left: 0.5rem;
  white-space: nowrap;
}

.bf-inst.bf-hcard .band > .sub > .cat > svg {
  width: 0.85em;
  height: 0.85em;
  margin-right: 0.2rem;
  vertical-align: -0.1em;
}

/* Un desplegable con su «Nueva…» al lado (chore 90.647): la opción que falta se crea sin salir del formulario. En
   el teléfono el botón baja a su propia línea. */
/* ── LAS CATEGORÍAS COMO CARPETAS (chore 90.662), las de Paneles de Hugin ───────────────────────────────────────
   El árbol a la izquierda y la rejilla a la derecha, que es lo que cambia al elegir. Con ancho, la columna se queda
   pegada al techo al rodar (cambiar de carpeta no obliga a volver arriba); en el teléfono va ENCIMA, recogida a un
   alto con su propio desplazamiento, para no empujar las tarjetas fuera de la pantalla. */
.bf-folders {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* Más aire entre el árbol, los filtros y las tarjetas (chore 90.677): con un rem se leían pegados. */
  gap: 1.5rem;
}

.bf-folders-main {
  min-width: 0;
}

/* LA COLUMNA DE CARPETAS, como la de la galería de Paneles de Hugin (chore 90.677): SIN tarjeta —ni fondo ni canto—,
   la lista directamente sobre la página; de Organización sólo viene el «+» de su cabecera. */
.bf-folders-side {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
}

/* En el teléfono el árbol va entero, sin rueda propia (chore 90.676): con el «⋯» de cada carpeta, un árbol que rueda
   por dentro recortaba su menú. */
.bf-folders-side > .bf-ftree {
  min-width: 0;
}

@media (min-width: 900px) {
  .bf-folders {
    grid-template-columns: 16rem minmax(0, 1fr);
    align-items: start;
  }

  /* El panel de la vista desborda a lo ancho (`overflow-x: auto`) y eso lo hacía el contenedor de rueda del pegado:
     la columna no se pegaba y al rodar se iba bajo la cabecera (chore 90.690). `clip` recorta sin ser rueda. */
  .bf-admin-main > .bf-panel:has(> .bf-folders) {
    overflow-x: clip;
  }

  .bf-folders-side {
    position: sticky;
    /* Por debajo de la cabecera pegada del shell (~3.6rem), no bajo ella (chore 90.690). */
    top: 4.5rem;
    max-height: calc(100dvh - 5.25rem);
  }

  .bf-folders-side > .bf-ftree {
    flex: 1;
    max-height: none;
    min-height: 0;
    /* Con ancho sí rueda por dentro: la columna va pegada al techo y un árbol largo no puede salirse de la vista. */
    overflow-y: auto;
    overscroll-behavior: contain;
  }
}

/* Filas pegadas: el aire lo ponen los rótulos de bloque, no el hueco entre filas. */
.bf-ftree {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.bf-fgroup {
  margin: 0.7rem 0 0.15rem;
  padding-left: 0.375rem;
  font-size: var(--bf-text-micro);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

.bf-frow {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.125rem;
  padding-left: calc(var(--d, 0) * 0.75rem);
  border-radius: var(--bf-radius-xs);
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-frow:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
}

/* La carpeta elegida, como la `.tree-row-active` de Paneles de Hugin (chore 90.729, a petición del usuario): el VELO
   de seleccionado (--bf-selected, el `--selected-color` de Hugin con sus mismos valores) que entra una vez desde
   transparente, el nombre a tinta entera y en negrita, y el glifo en el acento. Era un lavado naranja del 12 %.
   Movimiento reducido: el velo se queda, sin la entrada. */
.bf-frow.on {
  background-color: var(--bf-selected);
  animation: bf-set-select-in var(--bf-dur-base) var(--bf-ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .bf-frow.on {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-frow.on {
  animation: none;
}

.bf-frow > .tw {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.2rem;
  height: 1.6rem;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-xs);
  background: none;
  color: var(--bf-muted);
  cursor: pointer;
}

.bf-frow > .tw > svg {
  width: 0.8rem;
  height: 0.8rem;
  transition: transform var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-frow > .tw[aria-expanded="true"] > svg {
  transform: rotate(90deg);
}

.bf-frow > .fl {
  display: flex;
  flex: 1;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.375rem 0.375rem 0.375rem 0.125rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  text-decoration: none;
}

.bf-frow:hover > .fl,
.bf-frow.on > .fl {
  color: var(--bf-surface-fg);
}

.bf-frow.on > .fl {
  font-weight: 600;
}

.bf-frow > .fl > svg {
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
}

.bf-frow.on > .fl > svg {
  color: var(--bf-accent);
}

.bf-frow > .fl > .fn {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Cuántas hay dentro, en cifras de ancho fijo para que la columna no baile al pasar de 9 a 10. */
.bf-frow > .fl > .fc {
  flex: none;
  font-size: var(--bf-text-mini);
  font-variant-numeric: tabular-nums;
  color: var(--bf-muted);
}

.bf-frow > .fl:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
  border-radius: inherit;
}

/* DESTINO DE UN ARRASTRE: mientras se lleva una tarjeta, las carpetas se ofrecen con el trazo discontinuo de «aquí
   cabe», y la de debajo se enciende. La marca del cuerpo la pone ts/ui/reorder.ts (chore 90.980). */
body[data-dragging="card"] .bf-frow[data-folder] {
  outline: 1px dashed color-mix(in srgb, var(--bf-accent) 35%, transparent);
  outline-offset: -1px;
}

.bf-frow[data-drop-on="true"] {
  outline: 1px solid var(--bf-accent);
  outline-offset: -1px;
  background-color: color-mix(in srgb, var(--bf-accent) 16%, transparent);
}

/* La cabecera del rail: el rótulo callado y, al canto, el «+» de carpeta nueva (Organización de Hugin). */
.bf-ftree-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  /* A la izquierda, el mismo 0,125 rem que el buscador de debajo: el rótulo arranca en su canto (chore 90.796). */
  padding: 0.125rem 0.125rem 0.25rem 0.125rem;
}

.bf-ftree-head > .ttl {
  font-size: var(--bf-text-mini);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

/* Botón fantasma de sólo icono, 28 px: no pesa al lado del rótulo y se ve en cuanto se pasa por encima. */
.bf-iconbtn {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  margin-left: auto;
  border-radius: var(--bf-radius-btn);
  color: var(--bf-muted);
  text-decoration: none;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out), color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-iconbtn:hover {
  background-color: var(--bf-control-hover-bg);
  color: var(--bf-surface-fg);
  text-decoration: none;
}

.bf-iconbtn:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
}

.bf-iconbtn > svg {
  width: 1rem;
  height: 1rem;
}

/* EL BUSCADOR de la casa de Hugin (`.field-search`): sin canto, con el velo de los campos, la lupa a la izquierda y
   el aspa sólo cuando hay algo que limpiar. */
.bf-fsearch {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  height: 1.75rem;
  padding: 0 0.375rem 0 0.625rem;
  border: 1px solid transparent;
  border-radius: var(--bf-radius-input);
  background-color: var(--bf-field-bg);
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-fsearch[hidden] {
  display: none;
}

.bf-fsearch:hover {
  background-color: var(--bf-control-hover-bg);
}

.bf-fsearch:focus-within {
  background-color: var(--bf-field-search-focus-bg);
}

.bf-fsearch > svg {
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  color: var(--bf-muted);
}

.bf-fsearch > input {
  flex: 1;
  min-width: 0;
  height: 100%;
  border: 0;
  padding: 0;
  background: transparent;
  color: var(--bf-surface-fg);
  font: inherit;
  font-size: var(--bf-text-label);
  outline: none;
}

.bf-fsearch > input::-webkit-search-cancel-button {
  display: none;
}

.bf-fsearch > button {
  display: none;
  place-items: center;
  flex: none;
  width: 1.375rem;
  height: 1.375rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--bf-muted);
  cursor: pointer;
}

.bf-fsearch[data-filled="true"] > button {
  display: inline-grid;
}

.bf-fsearch > button:hover {
  background-color: var(--bf-control-hover-bg);
  color: var(--bf-surface-fg);
}

.bf-fsearch > button > svg {
  width: 0.75rem;
  height: 0.75rem;
}

.bf-ftree-find {
  flex: none;
  margin: 0 0.125rem 0.25rem;
}

/* LA BARRA DE FILTROS, la de Organización (chore 90.677): una línea sin caja; los desplegables compactos a la
   izquierda, el recuento callado y el buscador ESTRECHO al canto derecho. Con aire debajo antes de las tarjetas. */
.bf-ibar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 1.25rem;
}

.bf-ibar > .cnt {
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-ibar > .bf-ifind {
  width: 14rem;
  margin-left: auto;
}

@media (max-width: 619.98px) {
  /* En estrecho el buscador baja a su renglón y se estira, como allí. */
  .bf-ibar > .bf-ifind {
    order: -1;
    width: 100%;
    margin-left: 0;
  }
}

/* El «⋯» de cada carpeta (chore 90.676): asoma con el puntero encima, con el foco o en la elegida, y se queda puesto
   donde no hay puntero (en una pantalla táctil, invisible es «no existe»). */
.bf-frow > .bf-more {
  position: relative;
  flex: none;
  opacity: 0;
  transition: opacity var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-frow:hover > .bf-more,
.bf-frow.on > .bf-more,
.bf-frow > .bf-more:focus-within,
.bf-frow > .bf-more[open] {
  opacity: 1;
}

@media (hover: none) {
  .bf-frow > .bf-more {
    opacity: 1;
  }
}

.bf-frow > .bf-more > summary {
  width: 1.5rem;
  height: 1.5rem;
}

.bf-frow > .bf-more .pop {
  right: 0;
  left: auto;
  z-index: 5;
}

/* Colocado contra la ventana por instance-folders-manage.ts (chore 90.690): así no lo recorta la rueda del árbol ni
   lo tapa la cabecera de la página. */
.bf-frow > .bf-more .pop[data-fixed] {
  position: fixed;
  right: auto;
  bottom: auto;
  z-index: 60;
}

.bf-frow > .bf-more .pop .bf-btn > svg {
  width: 0.95rem;
  height: 0.95rem;
}

/* Las dadas de baja, apagadas: se ven, se reactivan, pero no son un sitio al que mover nada. */
.bf-frow.off > .fl {
  opacity: 0.6;
  text-decoration: line-through;
  text-decoration-color: color-mix(in srgb, var(--bf-muted) 50%, transparent);
}

.bf-frow[draggable="true"] {
  cursor: grab;
}

/* Arrastrando una CARPETA se ofrecen las otras carpetas activas y «Todas» (la raíz). */
body[data-dragging="folder"] .bf-frow[data-active="true"],
body[data-dragging="folder"] .bf-frow[data-root="true"] {
  outline: 1px dashed color-mix(in srgb, var(--bf-accent) 35%, transparent);
  outline-offset: -1px;
}

/* Las migas de la carpeta, encima de la barra de filtros. */
.bf-fcrumbs {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin: 0 0 0.5rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-fcrumbs a {
  color: inherit;
}

.bf-fcrumbs b {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-pickrow {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
}

.bf-pickrow > select {
  flex: 1 1 12rem;
  min-width: 0;
}

.bf-inst.bf-hcard > .pv > .state {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.bf-inst.bf-hcard > .pv > .state > b {
  font-weight: 600;
  color: var(--hc-ink);
}

.bf-inst.bf-hcard .lamp {
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--hc-tone);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--hc-tone) 20%, transparent);
}

.bf-inst.bf-hcard.off .lamp {
  box-shadow: none;
}

/* La licencia, un distintivo que cabe en el pie; sin vigencia, la pastilla ES el camino para arreglarlo. */
.bf-hcard .lic {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem;
  margin-right: auto;
}

.bf-hcard .lic .bf-pill {
  height: 1.125rem;
  padding: 0 0.45rem;
  font-size: var(--bf-text-micro);
  gap: 0.25rem;
}

.bf-hcard .lic a.bf-pill {
  text-decoration: none;
  transition: filter var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-hcard .lic a.bf-pill:hover {
  filter: brightness(1.25);
}

.bf-hcard .lic a.bf-pill > svg {
  width: 0.65rem;
  height: 0.65rem;
}

@media (prefers-reduced-motion: reduce) {
  .bf-hgrid > * {
    animation: none;
  }

  .bf-hcard,
  .bf-hcard:hover,
  .bf-hcard .tile {
    transform: none;
    transition: none;
  }
}

/* ═══ LA FICHA DE UNA INSTANCIA, VESTIDA (chore 90.596) ═══════════════════════════════════════════════════════════════
   Datos, Licencia y Salud eran tarjetas con listas de definición en dos columnas: todo el mismo peso, y lo que
   responde «¿cómo está?» se leía igual que la clave de un permiso. Esto es VESTIDO y sólo vestido —los datos, su orden
   y su lógica son los de antes— con el lenguaje que ya tienen las tarjetas de Hugin del listado:
     · la CABECERA del registro (`.bf-irec`) lleva el halo del tono de estado y la misma baldosa que su tarjeta, con el
       LED en la esquina: al abrir la ficha se reconoce la tarjeta de la que se viene;
     · cada tarjeta de pestaña (`.bf-ipanel`) lleva la ficha del glifo en su cabecera, el bisel del cristal y una entrada
       en cascada que, como la de la rejilla, no baja: se funde y crece desde su canto de arriba;
     · lo que es CIFRA va en baldosas hundidas (`.bf-istats`) —M-5: el relleno hundido es para piezas interiores
       rodeadas de superficie— y lo que es relación, en filas con filete (`.bf-irows`);
     · el estado se dice por tono + LED + texto (M-2), nunca sólo con color, y sin gritar: el tono es un halo, no un
       relleno.
   Cambiar de pestaña sigue siendo el fundido corto de la 90.447: aquella regla gana a la entrada de estas tarjetas, y
   la cascada sólo se ve al llegar a la ficha. */

/* ── La cabecera del registro ── */
.bf-irec {
  --ir-tone: var(--bf-success);
  position: relative;
  isolation: isolate;
  box-shadow: var(--bf-glass-bevel);
}

.bf-irec.warn {
  --ir-tone: var(--bf-warning);
}

.bf-irec.bad {
  --ir-tone: var(--bf-danger);
}

.bf-irec.off {
  --ir-tone: var(--bf-off);
}

/* El halo nace por la izquierda, detrás de la baldosa, y se apaga antes de llegar a las acciones. */
.bf-irec::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(38rem 9rem at 0% 50%,
      color-mix(in srgb, var(--ir-tone) 13%, transparent),
      color-mix(in srgb, var(--ir-tone) 3%, transparent) 55%,
      transparent 80%);
}

/* La retirada o inactiva baja de intensidad, como su tarjeta; lo dice también la pastilla «Inactiva». */
.bf-irec.dim::before {
  opacity: 0.45;
}

/* La baldosa de la instancia es NEUTRA, como en su tarjeta: el estado se lo dejan el halo, el LED y el texto. */
.bf-irec .bf-htile {
  position: relative;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.85rem;
  font-size: var(--bf-text-body);
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 10%, transparent);
  color: var(--bf-surface-fg);
}

.bf-irec .bf-htile svg {
  width: 1.35rem;
  height: 1.35rem;
}

/* La de un PRODUCTO sí va teñida de su color (chore 90.602), como en su tarjeta del listado: ahí el color es su
   identidad, no un estado. El estado se lo siguen dejando el halo, el LED y la pastilla. */
.bf-irec .bf-htile.tone {
  background: color-mix(in srgb, var(--hc-tone) 20%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hc-tone) 26%, transparent);
  color: color-mix(in srgb, var(--hc-tone) 70%, var(--bf-surface-fg));
}

/* El LED en la esquina de la baldosa, recortado contra la tarjeta con un aro de su color. */
.bf-irec .bf-htile > .lamp {
  position: absolute;
  right: -0.1875rem;
  bottom: -0.1875rem;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: var(--ir-tone);
  box-shadow:
    0 0 0 2px var(--bf-card-bg),
    0 0 0 5px color-mix(in srgb, var(--ir-tone) 22%, transparent);
}

.bf-irec.off .bf-htile > .lamp {
  box-shadow: 0 0 0 2px var(--bf-card-bg);
}

.bf-irec .bf-rechead .line b {
  font-size: var(--bf-text-lead);
  letter-spacing: -0.005em;
}

/* ── Las tarjetas de las pestañas ── */
.bf-ipanel {
  box-shadow: var(--bf-glass-bevel);
  transform-origin: 50% 0;
  animation: bf-ipanel-in var(--bf-dur-side) var(--bf-ease-pop) backwards;
  animation-delay: calc(min(var(--i, 0), 6) * var(--bf-dur-fast) / 2);
}

@keyframes bf-ipanel-in {
  from {
    opacity: 0;
    transform: scale(0.985);
  }
}

/* La cabecera envuelve (MF-5): la de Licencia lleva el título y hasta tres mandos, y a ancho de teléfono los mandos
   bajan a su renglón en vez de salirse. El título se queda con el sitio y empuja lo demás a la derecha. */
.bf-ipanel > header {
  flex-wrap: wrap;
  row-gap: 0.5rem;
}

.bf-ipanel > header > h3 {
  flex: 1 1 auto;
  min-width: 0;
}

/* La ficha del glifo: la marca de la sección, como la del carril (M-8), no un estado. */
.bf-ipanel > header > .ic {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--bf-radius-btn);
  background: color-mix(in srgb, var(--bf-accent) 13%, transparent);
  color: var(--bf-accent-text);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 22%, transparent);
}

.bf-ipanel > header > .ic > svg {
  width: 1rem;
  height: 1rem;
}

/* El glifo en la ficha del carril (el formulario de la instancia lo pinta ahí también en el modal del alta). */
.bf-frail .fic > svg {
  width: 1.125rem;
  height: 1.125rem;
}

/* ── Las cifras ──
   Baldosas hundidas con el rótulo arriba y el valor con presencia. Nacen de dos en dos a ancho de teléfono (8,5 rem
   caben dos veces en 296 px) y se reparten solas hasta cinco en una fila ancha. La que lleva TONO lo dice con un halo
   desde su esquina, sin filo en el canto (chore 90.626); su LED y su texto siguen diciéndolo sin color. */
.bf-istats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(8.5rem, 100%), 1fr));
  gap: 0.625rem;
  margin: 0;
}

.bf-istats > div {
  --is-tone: transparent;
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
  padding: 0.75rem 0.875rem 0.8125rem;
  border-radius: var(--bf-radius-btn);
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-border);
  overflow: hidden;
}

.bf-istats > .tone.ok {
  --is-tone: var(--bf-success);
}

.bf-istats > .tone.warn {
  --is-tone: var(--bf-warning);
}

.bf-istats > .tone.bad {
  --is-tone: var(--bf-danger);
}

.bf-istats > .tone.idle {
  --is-tone: var(--bf-off);
}

.bf-istats > .tone {
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--is-tone) 13%, transparent), transparent 70%),
    var(--bf-panel-bg);
}

.bf-istats dt {
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

.bf-istats dd {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.5rem;
  min-width: 0;
  margin: 0;
}

.bf-istats .v {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  font-size: var(--bf-text-head);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--bf-surface-fg);
  overflow-wrap: anywhere;
}

/* Una fecha con su hora no cabe a cuerpo de cifra en media baldosa de teléfono: baja al de entradilla. */
.bf-istats .v.sm,
.bf-istats .v.mono {
  font-size: var(--bf-text-lead);
}

.bf-istats .v.mono {
  font-family: var(--bf-font-mono);
  letter-spacing: 0;
}

/* El LED de una cifra lleva su aro, como la lámpara de la tarjeta: es luz, y la cifra es grande. */
.bf-istats .v > .bf-led {
  width: 0.5rem;
  height: 0.5rem;
  box-shadow: 0 0 0 3px color-mix(in srgb, currentColor 20%, transparent);
}

.bf-istats .v > .bf-led.idle {
  box-shadow: none;
}

/* Los topes de la licencia: la clave arriba, en su etiqueta, y la cifra sola —sin barra ni fracción: Bifrost no conoce
   el consumo—. Se reparten a lo que quepa sin estirar la última a todo el ancho. */
.bf-ilimits {
  grid-template-columns: repeat(auto-fill, minmax(min(9rem, 100%), 1fr));
}

/* ── Los grupos, con su rótulo y un filete que corre hasta el canto ── */
.bf-igroup > * + * {
  margin-top: 0.625rem;
}

.bf-isub {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
}

.bf-isub::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--bf-border);
}

.bf-ifeats {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

/* ── Las relaciones, en filas con filete ──
   La misma lista de definición (`.bf-dl`), con aire y el filete de superficie entre fila y fila. El rótulo toma 7,25
   rem a ancho de teléfono y 11 desde el corte medio: con un `minmax` el carril del rótulo se comería el del valor. */
.bf-dl.bf-irows {
  grid-template-columns: 7.25rem minmax(0, 1fr);
  gap: 0 1rem;
}

@media (min-width: 620px) {
  .bf-dl.bf-irows {
    grid-template-columns: 11rem minmax(0, 1fr);
  }
}

.bf-irows > dt,
.bf-irows > dd {
  padding-block: 0.625rem;
  border-top: 1px solid var(--bf-border);
  line-height: 1.5rem;
}

.bf-irows > dt:first-of-type,
.bf-irows > dd:first-of-type {
  padding-top: 0;
  border-top: 0;
}

.bf-irows > dt:last-of-type,
.bf-irows > dd:last-of-type {
  padding-bottom: 0;
}

/* La nota de diagnóstico bajo una pastilla (la versión declarada del mapa, sus vueltas y el motivo). */
.bf-irows > dd > div {
  margin-top: 0.25rem;
  line-height: 1.45;
}

/* ── La tendencia, a su tamaño de ficha ──
   Doce franjas sobre una rejilla de cuartos, con el filete fuerte de base. Cada franja lleva su tono en degradado
   —más presente arriba, donde se lee la altura— y crece desde su pie al entrar, una detrás de otra (`--i`). La hora sin
   sondeo sigue apagada y plana: no se le da volumen a lo que nadie midió (chore 90.217). */
.bf-itrend .spark {
  height: 5rem;
  gap: 0.3125rem;
  background: repeating-linear-gradient(to top, var(--bf-border) 0 1px, transparent 1px 25%);
  border-bottom: 1px solid var(--bf-border-strong);
}

.bf-itrend .spark i {
  --bar: var(--bf-success);
  border-radius: var(--bf-radius-xs) var(--bf-radius-xs) 1px 1px;
  background: linear-gradient(to top,
      color-mix(in srgb, var(--bar) 30%, transparent),
      color-mix(in srgb, var(--bar) 78%, transparent));
  transform-origin: 50% 100%;
  animation: bf-itrend-grow var(--bf-dur-side) var(--bf-ease-pop) backwards;
  animation-delay: calc(var(--bf-dur-base) + var(--i, 0) * var(--bf-dur-fast) / 4);
}

.bf-itrend .spark i.bad {
  --bar: var(--bf-danger);
}

.bf-itrend .spark i.none {
  --bar: var(--bf-off);
  background: color-mix(in srgb, var(--bar) 26%, transparent);
}

@keyframes bf-itrend-grow {
  from {
    opacity: 0;
    transform: scaleY(0.05);
  }
}

/* Al pie, la clave de los tonos con su texto (M-2) y la escala; envuelven antes que desbordar. */
.bf-itrend .foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  margin-top: 0.75rem;
}

.bf-itrend .key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.875rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-itrend .key li {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
}

/* ── La pestaña Datos: el formulario de la ficha entra igual, sección a sección ──
   Sólo en la ficha de una instancia que existe (la cabecera del registro va delante); el alta y su modal, no. */
.bf-irec ~ .bf-form > .bf-form-row,
.bf-irec ~ .bf-form-row {
  transform-origin: 50% 0;
  animation: bf-ipanel-in var(--bf-dur-side) var(--bf-ease-pop) backwards;
}

.bf-irec ~ .bf-form > .bf-form-row:nth-of-type(2) {
  animation-delay: calc(var(--bf-dur-fast) / 2);
}

.bf-irec ~ .bf-form > .bf-form-row:nth-of-type(3) {
  animation-delay: var(--bf-dur-fast);
}

.bf-irec ~ .bf-form-row {
  animation-delay: calc(var(--bf-dur-fast) * 1.5);
}

.bf-irec ~ .bf-form .bf-sect,
.bf-irec ~ .bf-form-row .bf-sect {
  box-shadow: var(--bf-glass-bevel);
}

.bf-iform .bf-frail .fic,
.bf-irec ~ .bf-form .bf-frail .fic,
.bf-irec ~ .bf-form-row .bf-frail .fic {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 22%, transparent);
}

/* Cambiar de pestaña es el fundido corto de la 90.447, también para las secciones de dentro. */
[data-nav-tab] .bf-irec ~ .bf-form > .bf-form-row,
[data-nav-tab] .bf-irec ~ .bf-form-row {
  animation: none;
}

@media (prefers-reduced-motion: reduce) {
  .bf-ipanel,
  .bf-itrend .spark i,
  .bf-irec ~ .bf-form > .bf-form-row,
  .bf-irec ~ .bf-form-row {
    animation: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-ipanel, .bf-itrend .spark i, .bf-irec ~ .bf-form > .bf-form-row, .bf-irec ~ .bf-form-row) {
  animation: none;
}

/* ═══ LA FICHA DE UNA INSTANCIA, PREMIUM (chore 90.716) ═══════════════════════════════════════════════════════════════
   Sobre el vestido de la 90.596, Datos, Licencia y Salud pasan al CRISTAL FINO de General (90.709/90.710) y ganan
   jerarquía: en Licencia manda la vigencia —cifra grande y una línea de HOY al fin de la cobertura—; en Salud, el
   estado solo y en grande, con las entregas en baldosas con su semáforo; en la cabecera del registro, los hechos con su
   glifo. Todo lleva la clase `.bf-iglass` (o cuelga de la cabecera que la lleva) y NO toca `.bf-ipanel` ni `.bf-isub`
   a secas, que también visten Mis sesiones y Avisos. Sin caída en reposo (90.717): el canto fino y nada más. Menos
   transparencia: macizo; colores forzados: el filete del sistema. */

/* ── El material: el cristal de General, en la cabecera, las tarjetas de pestaña y las secciones de Datos ──
   Las secciones se enganchan también por el propio formulario (`.bf-iform`, chore 90.718): el alta que abre el
   catálogo de instancias (`/admin/instances/new`) no tiene cabecera de registro delante y se quedaba con la tarjeta
   de siempre. En el modal del alta no aplica: allí `instance-new.ts` deshace las `.bf-sect` en apartados. */
.bf-iglass,
.bf-iform .bf-sect,
.bf-irec.bf-iglass ~ .bf-form .bf-sect,
.bf-irec.bf-iglass ~ .bf-form-row .bf-sect {
  --if-well: color-mix(in srgb, var(--bf-panel-bg) 55%, transparent);
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  box-shadow: none; /* 90.764: el filete lo pone el contorno del bloque de cantos; con éste, salía doble */
}

@media (prefers-reduced-transparency: reduce) {
  .bf-iglass,
  .bf-iform .bf-sect,
  .bf-irec.bf-iglass ~ .bf-form .bf-sect,
  .bf-irec.bf-iglass ~ .bf-form-row .bf-sect {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-iglass, .bf-iform .bf-sect, .bf-irec.bf-iglass ~ .bf-form .bf-sect, .bf-irec.bf-iglass ~ .bf-form-row .bf-sect) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-iglass,
  .bf-iform .bf-sect,
  .bf-irec.bf-iglass ~ .bf-form .bf-sect,
  .bf-irec.bf-iglass ~ .bf-form-row .bf-sect {
    border-color: CanvasText;
  }
}

/* ── La cabecera del registro: baldosa y nombre con más presencia, y los hechos con su glifo ── */
.bf-irec.bf-iglass .bf-htile {
  width: 3.25rem;
  height: 3.25rem;
  border-radius: 1rem;
}

.bf-irec.bf-iglass .bf-htile svg {
  width: 1.5rem;
  height: 1.5rem;
}

.bf-irec.bf-iglass .bf-rechead {
  gap: 0.875rem 1rem;
  padding: 1rem 1.125rem;
}

.bf-irec.bf-iglass .bf-rechead .line b {
  font-size: var(--bf-text-head);
  letter-spacing: -0.015em;
}

.bf-ifacts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0.5rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-ifacts > li {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  max-width: 100%;
  padding: 0.1875rem 0.5625rem 0.1875rem 0.4375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-ifacts > li > svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  color: var(--bf-dim);
}

.bf-ifacts > li > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La versión trae el hash de la compilación: se recorta y la entera va en el `title`. */
.bf-ifacts > li.ver > span {
  max-width: 13rem;
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-mini);
}

/* ── Las piezas interiores, sobre el cristal: un pozo fino con su canto, no una bandeja ── */
.bf-iglass .bf-istats > div {
  background: var(--if-well);
  box-shadow: var(--bf-lg-edge);
}

.bf-iglass .bf-istats > .tone {
  background:
    radial-gradient(120% 140% at 0% 0%, color-mix(in srgb, var(--is-tone) 13%, transparent), transparent 70%),
    var(--if-well);
}

/* El rótulo de grupo lleva su cuenta en una muesca, antes del filete. */
.bf-iglass .bf-isub > .n {
  margin-left: -0.375rem;
  padding: 0 0.4375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  font-size: var(--bf-text-mini);
  font-weight: 600;
  line-height: 1.25rem;
  font-variant-numeric: tabular-nums;
  color: var(--bf-dim);
}

/* El HUECO que explica: un vacío de grupo no es un error, y el glifo y el filete discontinuo lo dicen. */
.bf-ivoid {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  padding: 0.75rem 0.875rem;
  border: 1px dashed color-mix(in srgb, var(--bf-surface-fg) 16%, transparent);
  border-radius: var(--bf-radius-btn);
  font-size: var(--bf-text-label);
  line-height: 1.4;
  color: var(--bf-muted);
}

.bf-ivoid > svg {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--bf-dim);
}

/* ── Licencia: la vigencia ──
   Nace en una columna; desde el corte ancho, la cifra a la izquierda y la línea a la derecha, con un filete entre las
   dos. El tono (ok · warn · bad) tiñe la cifra cuando avisa, el halo de la esquina y el tramo vigente. */
.bf-ilic {
  --il-tone: var(--bf-success);
  display: grid;
  gap: 1rem 1.5rem;
  padding: 1.125rem 1.25rem;
  border-radius: var(--bf-radius-l);
  background:
    radial-gradient(70% 140% at 0% 0%, color-mix(in srgb, var(--il-tone) 11%, transparent), transparent 72%),
    var(--if-well);
  box-shadow: var(--bf-lg-edge);
}

.bf-ilic.warn {
  --il-tone: var(--bf-warning);
}

.bf-ilic.bad {
  --il-tone: var(--bf-danger);
}

@media (min-width: 900px) {
  .bf-ilic {
    grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr);
    align-items: center;
  }

  .bf-ilic > .line {
    padding-left: 1.5rem;
    border-left: 1px solid var(--bf-border);
  }
}

.bf-ilic .lbl {
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

.bf-ilic .num {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0.125rem 0 0;
}

.bf-ilic .num > b {
  font-size: calc(var(--bf-text-hero) * 1.75);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: -0.035em;
  font-variant-numeric: tabular-nums;
  color: var(--bf-surface-fg);
}

.bf-ilic .num > span {
  font-size: var(--bf-text-lead);
  font-weight: 500;
  color: var(--bf-muted);
}

/* «Caducada» en lugar de la cifra: una palabra, a cuerpo de título. */
.bf-ilic .num.word > b {
  font-size: calc(var(--bf-text-hero) * 1.25);
  letter-spacing: -0.02em;
}

.bf-ilic:is(.warn, .bad) .num > b {
  color: color-mix(in srgb, var(--il-tone) 72%, var(--bf-surface-fg));
}

.bf-ilic .when {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.5rem;
  margin: 0.5rem 0 0;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

/* La línea: tramos con el peso de sus días y un mínimo para que la gracia corta exista. */
.bf-ilic .bar {
  position: relative;
  display: flex;
  align-items: center;
  gap: 3px;
  height: 0.625rem;
}

.bf-ilic .seg {
  --seg: var(--il-tone);
  flex: var(--g, 1) 1 0;
  min-width: 0.875rem;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg,
      color-mix(in srgb, var(--seg) 55%, transparent),
      color-mix(in srgb, var(--seg) 88%, transparent));
}

.bf-ilic .seg.grace {
  --seg: var(--bf-warning);
  background: repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--seg) 75%, transparent) 0 3px,
      color-mix(in srgb, var(--seg) 32%, transparent) 3px 6px);
}

.bf-ilic .seg.used {
  --seg: var(--bf-off);
  background: color-mix(in srgb, var(--seg) 30%, transparent);
}

.bf-ilic .seg.none {
  --seg: var(--bf-danger);
}

/* HOY: una marca de ancho cero entre tramos, un punto con el aro de la superficie. */
.bf-ilic .now {
  position: relative;
  flex: none;
  width: 0;
  height: 100%;
}

.bf-ilic .now::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 0.875rem;
  height: 0.875rem;
  border: 2px solid var(--bf-surface-fg);
  border-radius: 50%;
  background: var(--bf-card-bg);
  transform: translate(-50%, -50%);
}

.bf-ilic .ends {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 0.625rem;
  font-size: var(--bf-text-label);
  font-variant-numeric: tabular-nums;
  color: var(--bf-muted);
}

.bf-ilic .ends > span:last-child {
  text-align: right;
}

.bf-ilic .key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-ilic .key li {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
}

/* La muestra de la clave: el mismo relleno que su tramo (lo hereda de las reglas de arriba por la clase). */
.bf-ilic .sw {
  --seg: var(--il-tone);
  flex: none;
  width: 0.875rem;
  height: 0.4375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--seg) 80%, transparent);
}

.bf-ilic .sw.grace {
  background: repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--bf-warning) 75%, transparent) 0 2px,
      color-mix(in srgb, var(--bf-warning) 32%, transparent) 2px 4px);
}

.bf-ilic .sw.used {
  background: color-mix(in srgb, var(--bf-off) 30%, transparent);
}

.bf-ilic .sw.none {
  background: color-mix(in srgb, var(--bf-danger) 80%, transparent);
}

/* ── Licencia: los hechos (producto, firma, entrega, gracia), en fichas con su glifo ── */
.bf-ichips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: 0.5rem;
  margin: 0;
}

.bf-ichips > div {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.375rem 0.75rem;
  min-width: 0;
  padding: 0.625rem 0.75rem;
  border-radius: var(--bf-radius-btn);
  background: var(--if-well);
  box-shadow: var(--bf-lg-edge);
}

.bf-ichips dt {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-ichips dt > svg {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--bf-dim);
}

.bf-ichips dd {
  margin: 0;
  font-size: var(--bf-text-label);
  font-weight: 600;
}

/* Topes y características, lado a lado desde el corte medio. */
.bf-iterms {
  display: grid;
  gap: 1.25rem 1.5rem;
}

@media (min-width: 900px) {
  .bf-iterms {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }
}

/* ── Salud: el estado, solo y en grande ──
   La luz es un LED dentro de dos aros de su tono pintados con un degradado (sin sombra y sin bucle: M-13), y al lado
   el estado a cuerpo de cifra con desde cuándo. Desde el corte ancho, las cifras del despliegue a su derecha. */
.bf-ipulse {
  --ip-tone: var(--bf-success);
  display: grid;
  gap: 0.625rem;
}

.bf-ipulse.warn {
  --ip-tone: var(--bf-warning);
}

.bf-ipulse.bad {
  --ip-tone: var(--bf-danger);
}

.bf-ipulse.idle {
  --ip-tone: var(--bf-off);
}

@media (min-width: 900px) {
  .bf-ipulse {
    grid-template-columns: minmax(15rem, 21rem) minmax(0, 1fr);
  }
}

.bf-ipulse > .state {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-width: 0;
  padding: 1rem 1.125rem;
  border-radius: var(--bf-radius-l);
  background:
    radial-gradient(110% 160% at 0% 0%, color-mix(in srgb, var(--ip-tone) 15%, transparent), transparent 70%),
    var(--if-well);
  box-shadow: var(--bf-lg-edge);
}

.bf-ipulse .orb {
  display: grid;
  place-items: center;
  flex: none;
  width: 3rem;
  height: 3rem;
  border-radius: 50%;
  background: radial-gradient(circle,
      color-mix(in srgb, var(--ip-tone) 26%, transparent) 0 36%,
      color-mix(in srgb, var(--ip-tone) 12%, transparent) 37% 64%,
      color-mix(in srgb, var(--ip-tone) 6%, transparent) 65% 100%);
}

.bf-ipulse .orb > .bf-led {
  width: 0.75rem;
  height: 0.75rem;
}

.bf-ipulse .txt {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
}

.bf-ipulse .txt > .lbl {
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

.bf-ipulse .txt > b {
  font-size: calc(var(--bf-text-hero) * 1.25);
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--bf-surface-fg);
  overflow-wrap: anywhere;
}

.bf-ipulse .txt > .since {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

/* Las cifras al lado del estado: misma altura que él desde el corte ancho, y la versión recortada en una línea. */
.bf-ipulse > .bf-istats > div {
  justify-content: center;
}

.bf-ipulse .v.mono {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-ipulse .v.off {
  font-weight: 500;
  color: var(--bf-muted);
}

/* ── Salud: lo que se le entrega, en baldosas con su glifo y su semáforo ── */
.bf-iflow {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(14rem, 100%), 1fr));
  gap: 0.625rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bf-iflow > li {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  min-width: 0;
  padding: 0.75rem 0.875rem;
  border-radius: var(--bf-radius-btn);
  background: var(--if-well);
  box-shadow: var(--bf-lg-edge);
}

.bf-iflow .ic {
  display: grid;
  place-items: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border-radius: var(--bf-radius-btn);
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  color: var(--bf-muted);
}

.bf-iflow .ic > svg {
  width: 1rem;
  height: 1rem;
}

.bf-iflow li > div {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.375rem;
  min-width: 0;
}

.bf-iflow li > div > b {
  font-size: var(--bf-text-label);
  font-weight: 500;
  color: var(--bf-muted);
}

.bf-iflow .val {
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-iflow .diag {
  margin: 0;
  font-size: var(--bf-text-mini);
  line-height: 1.45;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
}

/* ── Salud: la tendencia, con su resumen en la cabecera y las horas sin dato visibles ── */
.bf-itrend-sum {
  display: inline-flex;
  align-items: baseline;
  gap: 0.125rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-itrend-sum > b {
  font-size: var(--bf-text-lead);
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-itrend-sum > em {
  margin-left: 0.625rem;
  font-style: normal;
  font-weight: 600;
  color: color-mix(in srgb, var(--bf-danger) 75%, var(--bf-surface-fg));
}

/* La hora sin sondeo sigue plana (90.217), pero se ve: un poco más de tinta para que la serie no parezca vacía. */
.bf-iglass .bf-itrend .spark i.none {
  background: color-mix(in srgb, var(--bf-off) 42%, transparent);
}

/* ── Datos: el estado de la instancia, como una baldosa dentro de su sección ── */
.bf-iform .bf-sect .bf-switch-row,
.bf-irec.bf-iglass ~ .bf-form .bf-switch-row {
  flex-direction: row;
  align-items: center;
  padding: 0.75rem 0.875rem;
  border-radius: var(--bf-radius-btn);
  background: var(--if-well, color-mix(in srgb, var(--bf-panel-bg) 55%, transparent));
  box-shadow: var(--bf-lg-edge);
}

/* La orden de la dirección de Bifrost, separada de la ayuda que la explica. */
.bf-irec.bf-iglass ~ .bf-form-row form > .bf-btn {
  margin-top: 0.625rem;
}

/* ═══ LA TABLA DE HUGIN (chores 90.456 y 90.457) ═══════════════════════════════════════════════════════════════════
   La de su pantalla de gestión (Manage.cshtml y manage.css): la tarjeta con el velo del panel al 24 % en oscuro y al
   60 % en claro, su pelo y su radio; la cabecera PEGADA y esmerilada, con los rótulos a 12 px en tinta apagada, SIN
   versalitas ni espaciado; filas apretadas (6 px por 8), el pelo flojo entre ellas, sin cebra, y la fila entera se
   aclara al apuntarla. El nombre a peso medio, lo demás a 12 px apagado, y las acciones en el «···» del final. */
.bf-htable {
  --ht-veil: 24%;
  border: 1px solid var(--ht-line, var(--bf-border));
  border-radius: var(--bf-radius-modal);
  background-color: color-mix(in srgb, var(--bf-surface-bg) var(--ht-veil), transparent);
  box-shadow: none;
}

:root[data-theme="light"] .bf-htable {
  --ht-veil: 60%;
}

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .bf-htable {
    --ht-veil: 60%;
  }
}

/* OSCURO SIN TONOS AZULES (chore 90.731): la banda de cabecera (#2c3239 de --bf-surface-bg, que tira a azul) y el
   realce de fila (--bf-hover) se cambian por grises NEUTROS, un punto cálidos para compensar la mesa fría de debajo
   —medidos en pantalla: cabecera #222222—, y los pelos pasan a blanco translúcido. El CUERPO tampoco: el velo de la
   tarjeta (90.746, el color de los desplegables) deja ver la mesa fría (#0e1319 medido), así que la tabla lleva su
   propio velo neutro, cálido al 70 % para que lo de debajo no lo enfríe. En claro no cambia nada.
   La cabecera y el velo de esta chore ya no están: la tabla siempre es `.bf-card.bf-htable` (AdminTableTagHelper) y la
   regla de la chore 90.781, más específica, los pisaba. Queda el realce de fila. */
:root[data-theme="dark"] .bf-htable {
  --ht-hover: color-mix(in srgb, var(--bf-row-hover-warm) 70%, transparent);
  --ht-line: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
  --bf-card-veil-hover: var(--bf-card-veil);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-htable {
    --ht-hover: color-mix(in srgb, var(--bf-row-hover-warm) 70%, transparent);
    --ht-line: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
    --bf-card-veil-hover: var(--bf-card-veil);
  }
}

.bf-htable .bf-table thead {
  background-color: var(--ht-head, color-mix(in srgb, var(--bf-surface-bg) calc(var(--ht-veil) + 28%), transparent));
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
  backdrop-filter: blur(12px) saturate(1.3);
}

.bf-htable .bf-table th {
  padding: 0.5rem;
  border-bottom: 0.5px solid color-mix(in srgb, var(--ht-line, var(--bf-border)) 80%, transparent);
  background-image: none;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
}

.bf-htable .bf-table td {
  padding: 0.375rem 0.5rem;
  border-bottom: 1px solid var(--ht-line, var(--bf-border));
  font-size: var(--bf-text-body);
}

.bf-htable .bf-table tr:last-child td {
  border-bottom: 0;
}

.bf-htable .bf-table tbody tr {
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-htable .bf-table tbody tr:hover {
  background-color: var(--ht-hover, var(--bf-hover));
}

.bf-htable .bf-table td.sub {
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
}

.bf-htable .bf-table td.nm {
  font-weight: 500;
}

.bf-htable .bf-table td.nm > a {
  color: inherit;
  text-decoration: none;
}

.bf-htable .bf-table td.nm > a:hover {
  color: var(--bf-accent-text);
}

.bf-htable .bf-table td.nm .bf-htile {
  display: inline-grid;
  width: 1.5rem;
  height: 1.5rem;
  margin-right: 0.5rem;
  border-radius: 0.45rem;
  font-size: var(--bf-text-micro);
  vertical-align: middle;
}

.bf-htable .bf-table td.nm .bf-htile svg {
  width: 0.85rem;
  height: 0.85rem;
}

.bf-htable .bf-table td.acts {
  overflow: visible;
  text-align: right;
}

/* El rótulo de las acciones, sobre su «···»; el estado, al tamaño del resto de datos de la fila. */
.bf-htable .bf-table th:last-child {
  text-align: right;
}

.bf-htable .bf-table td .bf-hstatus,
.bf-htable .bf-table td[data-health] {
  font-size: var(--bf-text-label);
}

/* La letra de ancho fijo se ve más grande a igual cuerpo: un punto menos para que la clave o la URL no destaquen. */
.bf-htable .bf-table td.mono {
  font-size: 0.72rem;
}

.bf-htable .bf-table td.acts .bf-more {
  display: inline-block;
}

.bf-htable .bf-table td.acts .bf-more > summary {
  opacity: 0.55;
}

.bf-htable .bf-table td.acts .bf-more > summary:hover,
.bf-htable .bf-table td.acts .bf-more[open] > summary,
.bf-htable .bf-table tbody tr:hover td.acts .bf-more > summary {
  opacity: 1;
}

/* AL ALTO DE LA VISTA (chore 90.463; el .fill-view de Hugin): desde 620 px la tarjeta llega AL MENOS al pie de la
   ventana —la mide `fillToView` en --fill-h— y el pie se queda abajo. Es un MÍNIMO, no un alto (chore 90.464, lo pidió
   el usuario): con un alto fijo la tabla rodaba por dentro en cuanto sus filas no cabían —medido a 1366×768, una barra
   para 3 px—; ahora crece con sus filas y, si pasan de la vista, rueda la página, como en el resto de pantallas. */
@media (min-width: 620px) {
  .bf-htable[data-fill] {
    display: flex;
    flex-direction: column;
    min-height: var(--fill-h, auto);
  }

  .bf-htable[data-fill] > .bf-table-wrap {
    flex: 1 0 auto;
    max-height: none;
  }

  .bf-htable[data-fill] > .bf-foot,
  .bf-htable[data-fill] > .bf-empty {
    flex: none;
  }
}

/* AL ALTO DE LA VISTA, Y NI UN PÍXEL MÁS (chore 90.573, la tabla de instancias): la misma medida de `fillToView`, pero
   como ALTO y no como mínimo. La tarjeta es una columna de flex con ese alto; el envoltorio de la tabla se queda con lo
   que dejen la banda y el pie (flex: 1 y min-height: 0, sin restas de alturas a mano) y rueda por dentro con la
   cabecera pegada, como las tablas de Hugin. La página ya no se alarga. El filete de la cabecera va pintado en el fondo
   de la celda por lo mismo que en .bf-table-wrap: con `border-collapse` el borde se queda atrás al rodar.
   En teléfono no aplica (MF): flujo normal, rueda la página. */
@media (min-width: 620px) {
  .bf-htable[data-fill][data-fit="view"] {
    height: var(--fill-h, auto);
    min-height: 0;
  }

  .bf-htable[data-fill][data-fit="view"] > .bf-table-wrap {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow: auto;
  }

  .bf-htable[data-fit="view"] .bf-table th {
    border-bottom-color: transparent;
    background-image: linear-gradient(to top, var(--bf-border) 1px, transparent 1px);
  }
}
/* El pie de la lista, el «25 de 33» de Hugin: pequeño y apagado. */
.bf-htable .bf-foot {
  border-top: 1px solid var(--bf-border);
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

/* EL PAGINADOR DE HUGIN (chore 90.484; `.pager-btn` e `.input-page` de su editor-panel.css): los cinco mandos con el
   velo de mando —no fantasmas como los de Latanur— y la caja de salto con el velo de campo, sin canto y sin rótulos a
   los lados: «página 2 de 6» ya lo dice el pie, a la izquierda. El apagado, con la tinta de deshabilitado. */
.bf-htable .bf-page > a,
.bf-htable .bf-page > span {
  background-color: var(--bf-control-bg);
}

.bf-htable .bf-page > a:hover {
  background-color: var(--bf-control-hover-bg);
}

.bf-htable .bf-page > span {
  opacity: var(--bf-disabled-ink);
}

.bf-htable .bf-page .jump .pre,
.bf-htable .bf-page .jump .of {
  display: none;
}

.bf-htable .bf-page .jump input {
  border-color: transparent;
  font-size: var(--bf-text-body);
}

.bf-htable .bf-page .jump input:hover:not(:focus) {
  background-image: none;
}

.bf-htable .bf-page .jump input:focus {
  border-color: var(--bf-cta-edge);
  box-shadow: none;
}

/* El conmutador de vista en la cabecera de página, a la altura de los botones. */
.bf-page-header .acts > .bf-tabs {
  align-self: center;
}

/* ── Los modos del menú de una instancia (chore 90.462) ────────────────────── */

/* La barra de encima del esquema: la pastilla Estándar/Personalizado y, en personalizado, lo que significa. Sin nota ni
   botón «Personalizar» en estándar (chore 90.575): la pastilla ya lo hace. */
.bf-menued-mode {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1rem;
  margin-bottom: 0.75rem;
}

.bf-menued-mode > .bf-tabs {
  align-self: center;
}

.bf-menued-mode > .bf-hint {
  margin: 0;
}

/* En ESTÁNDAR el esquema se mira, no se edita: sin asas ni mandos, sin agarre (la nota de personalizado se fue en la 90.672). */
.bf-menued.is-standard [data-role="reset"],
.bf-menued.is-standard [data-role="add-section"],
.bf-menued.is-standard [data-role="add-module"],
.bf-menued.is-standard .grip,
.bf-menued.is-standard .acts,
.bf-menued.is-standard .bf-menued-keys {
  display: none;
}

/* La cabecera de sección no: en estándar también pliega, y sigue con la mano de clic (chore 90.580). */
.bf-menued.is-standard .bf-menued-row[draggable="true"] {
  cursor: default;
}

/* «Nuevo»: lo que el manifiesto trae desde que se personalizó. No es un cambio de esta instancia. */
.bf-menued .bf-pill.fresh {
  cursor: help;
}

/* ── Matriz de acceso: personas por instancia ──────────────────────────────── */

/* RE-VESTIDA en la chore 90.261 (contrato de diseño §3). Sigue siendo matriz (M-11):
   personas en filas, instancias en columnas, un rol escrito en cada cruce. Cambian
   tres cosas:
     · LA CUENTA DEL SUELO, en rem. El suelo por columna (MF-5) iba en píxeles y la
       letra ya no: con la raíz fluida a 18 px el rol de la celda crecía y su columna
       no, y la matriz dejaba de caber en la columna que ella misma reservaba. Ahora
       la columna crece con la letra.
     · EL TONO DEL ROL deja de ser un color de estado (abajo, en `.bf-cell`).
     · Los filetes, los del sistema: el interior es el hilo translúcido de las
       superficies, y el de la cabecera, el separador (M-5), que al rodar pasa a la
       cabecera pegajosa de cristal de la tabla (90.259): la matriz vive en el mismo
       envoltorio y la hereda. */
.bf-matrix {
  overflow: hidden;
}

.bf-matrix table {
  width: 100%;
  border-collapse: collapse;
  table-layout: fixed;
  /* Suelo de la rejilla (chore 90.073 · MF-5): con `table-layout: fixed` y ancho
     100 %, seis instancias a 360 px dejaban la celda en 24 px y el nombre del rol
     no cabía. La tabla reserva la columna de la persona más un mínimo por cada
     instancia, y lo que sobra se desplaza DENTRO de su `.bf-table-wrap` —nunca la
     página—. El número de columnas lo pone la pantalla, que es la única que lo
     sabe: `style="--bf-matrix-cols: N"`. A la raíz de 16 son los 218 + N × 96 px de
     siempre; la columna de instancia son los 3,875rem de la celda más su relleno y
     un margen para un rol de nombre largo. */
  min-width: calc(13.625rem + (var(--bf-matrix-cols, 1) * 6rem));
}

/* La columna de la persona no se encoge: es la que dice de quién es la fila. */
.bf-matrix colgroup col:first-child {
  width: 13.625rem;
}

.bf-matrix th,
.bf-matrix td {
  border-bottom: 1px solid var(--bf-border);
  padding: 0;
}

.bf-matrix thead th {
  border-bottom: 1px solid var(--bf-border-strong);
  vertical-align: bottom;
  padding: 0.5rem 0.375rem;
  text-align: center;
}

.bf-matrix thead th .p {
  font-size: var(--bf-text-micro);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--bf-dim);
  display: block;
}

.bf-matrix thead th .i {
  font-size: var(--bf-text-label);
  font-weight: 600;
  display: block;
  margin-top: 0.125rem;
  line-height: 1.25;
}

/* El producto de la columna con su baldosa de color (chore 90.650), en minúscula de lectura: con veinte columnas, el
   color agrupa las del mismo producto antes de leerlas, y las versalitas gritaban veinte veces lo mismo. */
.bf-matrix thead th .p {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  max-width: 100%;
  letter-spacing: 0;
  text-transform: none;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-matrix thead th .p > .bf-htile {
  width: 1.1rem;
  height: 1.1rem;
  border-radius: 0.35rem;
  font-size: 0.55rem;
}

/* LAS PERSONAS EN TARJETAS (chore 90.650): la baldosa son sus iniciales, y debajo sus accesos por producto, con el
   color del rol como en la matriz y el número de instancias de ese producto. */
.bf-people .bf-hcard > .pv {
  gap: 0.6rem;
}

.bf-people .bf-hcard > .pv > .acc {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.3rem;
  max-width: 100%;
  max-height: 3.3rem;
  overflow: hidden;
  padding: 0 0.5rem;
}

.bf-people .bf-hcard > .pv > .acc > .bf-cell {
  min-width: 0;
  gap: 0.35rem;
  height: 1.375rem;
  padding: 0 0.45rem;
}

.bf-people .bf-hcard > .pv > .acc > .bf-cell > b {
  font-weight: 700;
  opacity: 0.7;
}

.bf-people .bf-hcard > .pv > .acc.none {
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-people .bf-hbadge.idp::before {
  content: "\25C7";
}

.bf-people-foot {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.75rem;
}

.bf-matrix thead th.who {
  text-align: left;
  padding-left: 1rem;
}

.bf-matrix tbody td {
  text-align: center;
  padding: 0.4375rem 0.375rem;
}

/* La celda de la persona es la CABECERA de su fila (`<th scope="row">`, chore
   90.073): en una rejilla, un lector de pantalla necesita las dos cabeceras para
   decir de quién y de dónde habla la celda que está leyendo. Se ve igual. */
.bf-matrix tbody td.who,
.bf-matrix tbody th.who {
  text-align: left;
  padding: 0.4375rem 1rem;
  font-weight: inherit;
}

/* LA CELDA, con la forma del chip de la casa (90.257): tinte del 15 % y letra en
   la tinta legible del tono, sin filete —el tinte ya la separa de la fila—. Los
   radios, el de muesca: es una pastilla pequeña dentro de una rejilla.

   EL TONO ES CATEGORÍA, NO ESTADO (chore 90.261 · S-6 y S-7). Sale del ORDEN del
   rol dentro de su producto, en ciclo de cuatro (UserInstanceRoleListingService),
   y sirve para que el mismo rol se reconozca de un vistazo en toda la columna. Se
   pintaba con los colores de estado —azul, verde, ámbar—, y en este sistema verde
   es «bien» y ámbar «no puedes»: un supervisor en ámbar parecía un acceso con un
   problema. Pasa a la SERIE DE DATOS (--bf-data-*), que es la paleta que el
   contrato reserva justo para distinguir cosas entre sí sin juzgarlas, y el cuarto
   es el neutro. El nombre del rol sigue escrito dentro (M-2): el color acompaña. */
.bf-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 1.5rem;
  min-width: 3.875rem;
  padding: 0 0.5625rem;
  border-radius: var(--bf-radius-xs);
  font-size: var(--bf-text-mini);
  font-weight: 600;
  border: 1px solid transparent;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-cell.r1 {
  background-color: color-mix(in srgb, var(--bf-data-1) 15%, transparent);
  color: var(--bf-data-1-text);
}

.bf-cell.r2 {
  background-color: color-mix(in srgb, var(--bf-data-2) 15%, transparent);
  color: var(--bf-data-2-text);
}

.bf-cell.r3 {
  background-color: color-mix(in srgb, var(--bf-data-3) 15%, transparent);
  color: var(--bf-data-3-text);
}

/* El cuarto, sin tinte de color: la tinta apagada sobre su propio 16 %, como el
   chip neutro. Hundido no: ahora el hundido es un velo que en claro casi no se ve. */
.bf-cell.r4 {
  background-color: color-mix(in srgb, var(--bf-muted) 16%, transparent);
  color: var(--bf-muted);
}

/* El hueco: sin acceso. Apagado entero (S-9), no un tono más. */
.bf-cell.none {
  color: var(--bf-dim);
  font-weight: 400;
  opacity: 0.5;
}

/* La concedida se abre en el cajón, y al apuntarla sube su tinte (S-5: el realce
   toca la superficie, no un canto). */
a.bf-cell.r1:hover {
  background-color: color-mix(in srgb, var(--bf-data-1) 24%, transparent);
}

a.bf-cell.r2:hover {
  background-color: color-mix(in srgb, var(--bf-data-2) 24%, transparent);
}

a.bf-cell.r3:hover {
  background-color: color-mix(in srgb, var(--bf-data-3) 24%, transparent);
}

a.bf-cell.r4:hover {
  background-color: color-mix(in srgb, var(--bf-muted) 24%, transparent);
}

/* Rombo: lo que viene del proveedor de identidad no se quita a mano. */
.bf-cell.idp::after {
  content: "◇";
  margin-left: 0.375rem;
  font-size: var(--bf-text-micro);
  opacity: 0.8;
}

/* La celda CONCEDIDA es un enlace a su detalle (chore 90.073): abre el cajón sin
   salir de la matriz. Se pinta igual que la de sólo lectura —el color lo pone su
   tono— y no se subraya, porque en una rejilla el subrayado la haría parecer otra
   cosa. El hueco no es enlace: no hay registro que enseñar. */
a.bf-cell,
a.bf-cell:hover {
  text-decoration: none;
}

/* La celda abierta queda realzada como ancla del cajón: se ve de dónde salió lo
   que se está leyendo al lado. */
.bf-cell.sel {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-legend {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  flex-wrap: wrap;
}

.bf-legend b {
  font-weight: 600;
  color: var(--bf-surface-fg);
}

/* Lo que la leyenda dice DE LA PIEZA y no de un estado («la línea inferior es la
   latencia de las últimas 12 h»): al otro extremo mientras quepa, y en su renglón
   cuando la leyenda envuelve (chore 90.076). */
.bf-legend .aside {
  margin-left: auto;
}

/* ── Tarjeta de producto y de proveedor ────────────────────────────────────── */

.bf-pcard {
  background: var(--bf-card-veil);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

.bf-pcard .top {
  padding: 15px 16px 12px;
  display: flex;
  gap: 12px;
  align-items: flex-start;
}

.bf-pcard .sq {
  width: 40px;
  height: 40px;
  border-radius: var(--bf-radius-l);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bf-accent-fg);
  font-weight: 700;
  font-size: 14px;
  flex: none;
}

.bf-pcard h3 {
  font-size: 15.5px;
  font-weight: 600;
}

.bf-pcard .key {
  font-family: var(--bf-font-mono);
  font-size: 11px;
  color: var(--bf-dim);
}

.bf-pcard .desc {
  font-size: 12.5px;
  color: var(--bf-muted);
  padding: 0 16px 12px;
}

/* El bloque de identidad manda en el renglón de arriba y la pastilla de estado se
   queda al final; `minmax(0)` en forma de min-width para que una clave larga no
   empuje la pastilla fuera (MF-5). */
.bf-pcard .top > div {
  flex: 1;
  min-width: 0;
}

/* Los DATOS de la tarjeta (chore 90.074): la lista de definición del catálogo, en la
   métrica corta que pide una tarjeta —el carril del rótulo baja de 116 a 76 px— y con
   el valor partiéndose antes que desbordar. La maqueta lo resuelve con un `style=` por
   tarjeta porque no tiene hoja; aquí es una regla. */
.bf-pcard .facts {
  padding: 0 16px 12px;
}

.bf-pcard .facts .bf-dl {
  grid-template-columns: 76px minmax(0, 1fr);
  font-size: 12px;
}

.bf-pcard .facts .bf-dl dd.mono {
  font-size: 11px;
}

/* Los mapeos A LA VISTA: la correspondencia grupo→rol es lo que más se consulta, y
   escondida tras un número obligaba a abrir la ficha para leerla. */
.bf-pcard .maps {
  padding: 0 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bf-pcard .maps .tags {
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
}

/* M-5: el pie se separa SÓLO con el filete, sin relleno tintado, igual que en la
   tarjeta de instancia. `margin-top: auto` lo pega abajo aunque las tarjetas de la
   rejilla tengan distinta altura. */
.bf-pcard .foot {
  margin-top: auto;
  border-top: 1px solid var(--bf-border-strong);
  padding: 8px 12px;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
}

/* La tarjeta de lo que NO está en servicio (chore 90.074): filete discontinuo, como el
   campo congelado. Nunca informa sola — la consecuencia va escrita dentro (M-2). */
.bf-pcard.off {
  border-style: dashed;
}

/* Lo mismo dicho con la tinta (chore 90.075): la tarjeta del producto retirado o sin
   desplegar baja de intensidad, como la de «Vega» en la maqueta. Tampoco informa sola —
   la pastilla de estado y el botón del pie («Registrar instancia») lo dicen por escrito. */
.bf-pcard.dim {
  opacity: 0.72;
}

/* Más acciones de una tarjeta (chore 90.075): el «···» de la maqueta, montado con
   <details> para que despliegue SIN JavaScript y se abra con el teclado. El panel se
   abre HACIA ARRIBA porque el pie está al final de la tarjeta y `.bf-pcard` recorta lo
   que se salga (overflow: hidden). */
.bf-more {
  position: relative;
  margin-left: auto;
}

.bf-more > summary {
  list-style: none;
  cursor: pointer;
  color: var(--bf-dim);
  letter-spacing: 0.12em;
  padding: 3px 7px;
  border-radius: var(--bf-radius-s);
  line-height: 1;
}

.bf-more > summary::-webkit-details-marker {
  display: none;
}

.bf-more > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
}

.bf-more[open] > summary {
  background: var(--bf-panel-bg);
  color: var(--bf-surface-fg);
}

/* EL PANEL ES CRISTAL (chore 90.258 · contrato §3 · S-4): la receta la pone
   .bf-glass —esta pieza está en su lista de selectores— y aquí van su sitio, su
   medida y su entrada. Sale DE la esquina de la que cuelga: abajo a la derecha,
   junto al «···», porque abre hacia arriba. Nace un pelo más abajo, un pelo a la
   derecha y al 96 %, y llega en 200 ms con la curva de salida; es el movimiento de
   los desplegables del sistema de origen (190–210 ms), y a 120 ms y sin recorrido lo
   que se veía era un parpadeo. Animación y no transición por la misma razón que el
   diálogo: el contenido de un <details> cerrado no se pinta, y al abrirse empieza.
   Sin JavaScript sigue siendo un <details>: abre, se lee y se pulsa igual. */
.bf-more .pop {
  position: absolute;
  right: 0;
  bottom: calc(100% + 6px);
  z-index: 5;
  min-width: 11rem;
  max-width: min(28rem, calc(100vw - 2rem));
  padding: 0.375rem;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  transform-origin: bottom right;
  animation: bf-pop-in var(--bf-dur-base) var(--bf-ease-out);
}

@keyframes bf-pop-in {
  from {
    opacity: 0;
    transform: translate(0.15rem, 0.4rem) scale(0.96);
  }
}

/* LAS ENTRADAS SON FILAS DE MENÚ, no botones apilados. Cada una sigue siendo el
   `.bf-btn` del formulario que la envía —es la misma acción que la de la fila de la
   tabla, compartida en _StateAction.cshtml, y funciona sin JavaScript—, así que aquí
   se le quita el traje de botón: sin filete ni velo, a la izquierda, en el cuerpo de
   menú y a su peso normal. El apuntado toca la superficie (S-5). En pantalla táctil
   sigue midiendo su área de 44 px: la regla de §13 es de .bf-btn y alcanza también
   a estas. */
.bf-more .pop .bf-btn {
  width: 100%;
  height: auto;
  justify-content: flex-start;
  padding: 0.375rem 0.625rem;
  border: 0;
  border-radius: var(--bf-radius-input);
  background-color: transparent;
  box-shadow: none;
  color: var(--bf-surface-fg);
  font-size: var(--bf-text-ui);
  font-weight: 400;
  line-height: 1.3;
  text-align: left;
}

.bf-more .pop .bf-btn:hover:not(:disabled) {
  background-color: var(--bf-hover);
  background-image: none;
}

/* EL ICONO DE CADA OPCIÓN (chore 90.595): dice la acción antes que la palabra. En
   la tinta apagada y a trazo de menú, para que la columna de iconos no pese más que
   las etiquetas; al apuntar la fila se enciende con ella. */
.bf-more .pop .bf-btn {
  gap: 0.625rem;
}

.bf-more .pop .bf-btn > svg {
  width: 1rem;
  height: 1rem;
  stroke-width: 1.8;
  color: var(--bf-muted);
  transition: color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-more .pop .bf-btn:is(:hover, :focus-visible) > svg {
  color: inherit;
}

/* LA DESTRUCTIVA, AL FINAL Y APARTADA. Va la última aunque el marcado la escriba
   antes (`order`), con un filete encima cuando hay algo antes que ella, y se tiñe
   SÓLO al apuntarla o enfocarla: una fila roja fija convierte el rojo en
   decoración, y entonces deja de avisar. Lo que sí es macizo es la confirmación que
   pide después (F-3). */
.bf-more .pop > :has(.bf-btn.danger) {
  order: 1;
}

.bf-more .pop > :has(.bf-btn.danger):not(:only-child) {
  margin-top: 0.25rem;
  padding-top: 0.25rem;
  border-top: 1px solid var(--bf-border);
}

.bf-more .pop .bf-btn.danger:hover:not(:disabled),
.bf-more .pop .bf-btn.danger:focus-visible {
  background-color: color-mix(in srgb, var(--bf-danger) 16%, transparent);
  color: var(--bf-danger-text);
}

.bf-bars {
  padding: 0 16px 12px;
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.bf-bar {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 11.5px;
}

.bf-bar .lbl {
  width: 74px;
  color: var(--bf-dim);
  flex: none;
}

.bf-bar .track {
  flex: 1;
  height: 6px;
  border-radius: 3px;
  background: var(--bf-border);
  overflow: hidden;
}

.bf-bar .track i {
  display: block;
  height: 100%;
  border-radius: 3px;
  background: var(--bf-accent);
}

/* Las tres barras de la tarjeta de producto son tres magnitudes distintas y se tiñen
   distinto (chore 90.075), como en la maqueta: parque en el acento, personas en el azul
   de información y catálogo en el verde. El color aquí NO es estado —no hay nada bueno
   ni malo en tener más permisos—, así que no usa la escala de M-2: cada barra lleva su
   rótulo y su número al lado. */
.bf-bar.people .track i {
  background: var(--bf-info);
}

.bf-bar.perms .track i {
  background: var(--bf-success);
}

.bf-bar .val {
  width: 52px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  color: var(--bf-muted);
}

/* SIN TOPE NO HAY BARRA (chore 90.139): donde iría el carril va el motivo —«sin tope
   contratable» o «sin licencia vigente»—, que ocupa su mismo hueco para que las tres
   filas de la tarjeta sigan alineadas. Una barra vacía diría «cero de algo»; esto dice
   que no hay contra qué medir, que es otra cosa. */
.bf-bar .why {
  flex: 1;
  color: var(--bf-dim);
  font-size: 11px;
}

/* PASARSE DEL TOPE sí es estado, y es la única lectura de esta pantalla que exige hacer
   algo: aquí el color SÍ informa (M-2), y por eso no viaja solo — el número de al lado
   dice «7 de 5» y el rótulo accesible del carril lo repite. */
.bf-bar.over .track i,
.bf-bar.people.over .track i {
  background: var(--bf-danger);
}

.bf-bar.over .val {
  color: var(--bf-danger-text);
  font-weight: 600;
}

/* Salud agregada: un punto por instancia. */
.bf-dotline {
  display: flex;
  gap: 5px;
  padding: 0 16px 12px;
}

.bf-dotline i {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--bf-success);
}

.bf-dotline i.w {
  background: var(--bf-warning);
}

.bf-dotline i.b {
  background: var(--bf-danger);
}

.bf-dotline i.o {
  background: var(--bf-border);
}

/* ── Árbol de catálogo: la sangría ES la dependencia ───────────────────────── */

/* RE-VESTIDO en la chore 90.261 (contrato de diseño §3, «piezas que solo tiene
   Bifrost»). Sigue siendo árbol (M-11): la sangría es la dependencia, el módulo
   agrupa y se pliega, y cada rol tiene su columna. Cambian:
     · la métrica, en rem —la sangría también: un nivel son 26 px a la raíz de 16 y
       crece con la letra, o el rótulo de un tercer nivel se comería su escalón—;
     · las MARCAS POR ROL, que pintaban un AJUSTE con los colores de un estado
       (abajo, en `.roles i`);
     · el modo reordenar: el nodo en vuelo es cristal que se levanta, los mandos de
       subir y bajar son mandos del catálogo, y el destino que no vale se pinta en
       ámbar, que es «no puedes», y no en rojo, que es «algo falló» (S-6). */
.bf-tree {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  list-style: none;
  margin: 0;
  padding: 0;
}

.bf-node {
  /* La sangría del nivel, como VARIABLE: la comparten el relleno de la fila y —en el
     modo reordenar— la línea de inserción, que tiene que caer exactamente donde va a
     quedar la categoría. Sin un único sitio donde vive, la línea y la fila mienten. */
  --bf-node-pad: 1rem;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 1rem 0.5rem var(--bf-node-pad);
  border-bottom: 1px solid var(--bf-border);
  font-size: var(--bf-table-row-font-size);
}

.bf-node:last-child {
  border-bottom: 0;
}

.bf-node.mod {
  background: var(--bf-panel-bg);
  font-weight: 600;
}

.bf-node.mod .caret {
  color: var(--bf-muted);
}

/* El galón mide siempre lo mismo esté o no dibujado: la cabecera de columnas
   lleva uno vacío, y sin ancho propio los rótulos de abajo no le cuadrarían. */
.bf-node .caret {
  flex: none;
  width: 0.75rem;
  text-align: center;
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

/* El galón del sprite apunta a la derecha; en el árbol abierto, hacia abajo. Sin hijas no se
   dibuja, pero conserva el hueco. */
.bf-node .caret > svg {
  display: block;
  width: 0.75rem;
  height: 0.75rem;
  transform: rotate(90deg);
}

.bf-node .caret.is-empty > svg {
  visibility: hidden;
}

.bf-node.lvl1 {
  --bf-node-pad: 2.5rem;
}

.bf-node.lvl2 {
  --bf-node-pad: 4.125rem;
}

/* Tercer nivel de dependencia (chore 90.079). La maqueta enseña dos porque su
   ejemplo tiene dos; un manifiesto puede declarar más, y el salto se mantiene en
   los mismos 26 px. Más abajo la vista no sigue sangrando: a partir de aquí la
   cadena se lee en la clave, y seguir sangrando sólo estrecharía el rótulo. */
.bf-node.lvl3 {
  --bf-node-pad: 5.75rem;
}

/* Cabecera de la matriz: es una fila del árbol sin serlo —no tiene permiso ni
   galón—, así que se dibuja plana y con el filete separador (M-5) que la corta. */
.bf-node.head {
  background: transparent;
  border-bottom: 1px solid var(--bf-border-strong);
  font-weight: 600;
}

.bf-node.head .roles i {
  background: transparent;
  border: 0;
  color: var(--bf-dim);
  font-weight: 600;
}

/* Cuántos permisos cuelgan del módulo. Va junto a las columnas y no dentro del
   nombre: el nombre se recorta con puntos suspensivos y el recuento no debe. */
.bf-node .count {
  flex: none;
  font-size: var(--bf-text-mini);
  color: var(--bf-dim);
}

/* Las marcas por rol nunca se encogen: son la respuesta de la pantalla. */
.bf-node .roles {
  flex: none;
}

/* Módulo plegable (chore 90.079) sobre <details>/<summary>: plegar y desplegar
   sin una línea de JavaScript, con teclado y con lector de pantalla, y con el
   estado ya expuesto por el navegador. El resumen por rol se lee plegado, que es
   para lo que existe: la forma del reparto sin desplegar nada. */
.bf-tree-group > summary {
  list-style: none;
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

/* El módulo que se pliega es un mando: al apuntarlo sube un velo sobre su hundido
   (S-5), el mismo realce que un botón. */
.bf-tree-group > summary.bf-node:hover {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-tree-group > summary::-webkit-details-marker {
  display: none;
}

/* La tinta del anillo doble (90.147) va `inset` y pisa a la del reset: hacia
   fuera invadiría la fila de al lado, que es justo lo que evita el offset
   negativo del anillo. */
.bf-tree-group > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
  box-shadow: inset 0 0 0 1px var(--bf-surface-fg);
}

.bf-tree-group[open] > summary .caret {
  transform: rotate(90deg);
}

/* El último filete del árbol lo pone el marco de la tarjeta. Con los módulos
   dentro de su <details>, el último nodo ya no es el último hijo del árbol. */
.bf-tree > .bf-tree-group:last-child > .bf-node:last-child {
  border-bottom: 0;
}

/* MF-5: el desborde es de la MATRIZ, no de la página. Con seis roles la fila no
   cabe en un teléfono, así que el árbol se arrastra dentro de su envoltorio con
   el ancho de lo que dice —igual que la tabla— y el rótulo del permiso sigue
   legible a 360 px en vez de repartirse a partes iguales. */
.bf-tree-wrap {
  overflow-x: auto;
}

.bf-tree-wrap > .bf-node,
.bf-tree-wrap > .bf-tree {
  min-width: 100%;
  width: max-content;
}

.bf-node .key {
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

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

/* ── Modo reordenar (chore 90.117) ─────────────────────────────────────────────
   El árbol entra en edición de ORDEN, que es un modo aparte: mientras dura, la
   pantalla no guarda nada hasta que se pulsa «Guardar orden». Lo pide el árbol de
   categorías (90.078), donde el orden decide en qué calle del portal cae cada
   instancia.

   Tres piezas y una barra:
     .grip    — manija de arrastre. Es el asa del ratón.
     .updown  — subir/bajar, la MISMA operación sin ratón. No es un adorno de
                accesibilidad: en un teléfono no hay arrastre que valga (MF-4), y
                sin estas dos flechas el modo entero sería inalcanzable.
     .moving  — el nodo en vuelo, con el carril de acento del «aquí estás».
     .bf-ordbar — la barra que dice en qué modo estás y CUÁNTOS cambios llevas sin
                guardar; el par Descartar / Guardar orden vive en la cabecera de
                página, que es donde el convenio pone las acciones del registro. */
.bf-node .grip {
  flex: none;
  color: var(--bf-dim);
  letter-spacing: 1px;
  font-size: var(--bf-text-ui);
  cursor: grab;
  user-select: none;
}

.bf-node .grip:active {
  cursor: grabbing;
}

/* Los seis puntos del asa son trazos de largo casi nulo: con el trazo fino del sprite, a este
   tamaño, apenas se verían. */
.bf-node .grip > svg {
  display: block;
  width: 0.875rem;
  height: 0.875rem;
  stroke-width: 3;
}

.bf-node .updown {
  display: flex;
  gap: 0.25rem;
  flex: none;
}

/* Subir y bajar son MANDOS del catálogo en pequeño (90.254): el velo del mando con
   su filete (M-12) y el realce apilado sobre el velo (S-5). El radio es el de la
   casilla y no el de mando: con 8 px en una caja de 22 el mando sale redondo y se
   lee como un botón de radio. */
.bf-node .updown i,
.bf-node .updown button {
  width: 1.375rem;
  height: 1.375rem;
  border-radius: var(--bf-radius-input);
  border: 1px solid var(--bf-button-line);
  background-color: var(--bf-button-bg);
  color: var(--bf-muted);
  font-style: normal;
  font-size: var(--bf-text-micro);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-node .updown button > svg {
  width: 0.75rem;
  height: 0.75rem;
}

.bf-node .updown button:hover:not(:disabled) {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-node .updown button:active:not(:disabled) {
  background-image:
    linear-gradient(var(--bf-hover), var(--bf-hover)),
    linear-gradient(var(--bf-hover), var(--bf-hover));
}

/* EL NODO EN VUELO: lo que se arrastra se LEVANTA (S-4, M-4 reescrito): la
   superficie elevada con la sombra de peso, que es la de lo que se arrastra en el
   sistema de origen —no el cristal, que es de lo que flota quieto—. Conserva el
   carril de acento del «aquí estás» (M-1): es el nodo que se está moviendo. Se
   eleva sobre sus vecinas para que la sombra no la tape la fila de debajo. */
.bf-node.moving {
  position: relative;
  z-index: 1;
  background-color: var(--bf-elevated-bg);
  box-shadow:
    inset 2px 0 0 var(--bf-accent),
    var(--bf-elevated-shadow);
}

/* ── Dónde va a caer (chore 90.138) ────────────────────────────────────────────
   Desde la 90.138 el arrastre hace DOS cosas —dejarla AL LADO o meterla DENTRO—,
   y cuál de las dos depende de la altura a la que se suelte dentro de la fila. Sin
   estas marcas eso se descubriría soltando, que es exactamente lo que había:
     .drop-before / .drop-after — línea de inserción en el filete de la fila,
                  arrancando en la SANGRÍA DEL DESTINO: donde empieza la línea es
                  donde va a quedar la categoría, y un nivel se distingue del otro.
     .drop-in    — el destino recuadrado, con el trazo corto a la sangría de sus
                  hijas: «dentro de ésta, un escalón más adentro».
     .drop-deny  — destino que el servidor rechazaría (sobre sí misma o sobre una
                  descendiente: sería un ciclo). Antes el único aviso era que el
                  cursor no picaba, y eso no lo lee nadie.
   Las pinta el módulo del slice mientras dura el arrastre y las retira al soltar.
   Sólo hacen falta donde se arrastra, así que el ancla del relativo es el árbol en
   modo reordenar: el árbol de permisos comparte .bf-node y no las quiere. */
.bf-tree[data-role="reorder"] .bf-node {
  position: relative;
}

.bf-node.drop-before::before,
.bf-node.drop-after::after {
  content: "";
  position: absolute;
  left: var(--bf-node-pad);
  right: 0;
  height: 2px;
  background: var(--bf-accent);
  pointer-events: none;
}

/* Encima del filete de arriba y del de abajo: la línea vive ENTRE dos filas, que es
   por donde se va a colar la categoría. */
.bf-node.drop-before::before {
  top: -1px;
}

.bf-node.drop-after::after {
  bottom: -1px;
}

/* «Aquí dentro»: el acento (M-1) sobre su tinte calculado, que ya no es el
   relleno opaco de la maqueta sino el 12 % del propio acento —se recompone sobre
   la tarjeta como los demás tintes del sistema—. */
.bf-node.drop-in {
  background-color: color-mix(in srgb, var(--bf-accent) 12%, transparent);
  box-shadow: inset 0 0 0 2px var(--bf-accent);
}

/* El escalón de las hijas son los mismos 26 px que separan un nivel del siguiente
   (1,625rem: la resta de dos sangrías, así que crece con ellas). */
.bf-node.drop-in::after {
  content: "";
  position: absolute;
  left: calc(var(--bf-node-pad) + 1.625rem);
  right: 0.5rem;
  bottom: 3px;
  height: 2px;
  background: var(--bf-accent);
  opacity: 0.6;
  pointer-events: none;
}

/* EL DESTINO QUE NO VALE es ÁMBAR (S-6): soltarla sobre sí misma o sobre una
   descendiente no es un fallo, es algo que no se puede hacer. El rojo se guarda
   para lo que falló, y un rojo que sale al pasar el ratón por encima de una fila
   enseña a no hacerle caso. El canto, en el color gráfico del estado (S-7). */
.bf-node.drop-deny {
  background-color: var(--bf-warning-soft);
  box-shadow: inset 0 0 0 2px var(--bf-warning);
  cursor: not-allowed;
}

/* Sin JavaScript el modo no puede acumular un borrador, así que sus mandos NO SE
   OFRECEN: ni manija que arrastrar ni flechas que no mueven (chore 90.078). El
   módulo marca el árbol al hidratar y entonces aparecen; mientras tanto se lee el
   árbol y el <noscript> dice dónde se cambia el orden — en el campo «Orden» de la
   ficha de cada categoría. Es el mismo recurso con el que la reserva de
   navegación se esconde cuando el shell sí hidrata. */
.bf-tree[data-role="reorder"]:not([data-hydrated]) .grip,
.bf-tree[data-role="reorder"]:not([data-hydrated]) .updown {
  display: none;
}

/* Mobile-first (MF-2): la barra NACE en dos renglones —a 360 px el rótulo, la
   explicación y el recuento no caben en línea— y el recuento se va a la derecha
   sólo cuando hay sitio. */
/* Re-vestida (chore 90.261) como el AVISO FIJO de la casa (90.257): es la franja
   que dice en qué modo estás mientras dura, y eso es un aviso informativo —canto
   en el color gráfico, fondo al 9 % y el radio del diálogo—, no una pieza hundida. */
.bf-ordbar {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.375rem 0.625rem;
  padding: 0.5625rem 0.875rem;
  border: 1px solid var(--bf-info);
  border-radius: var(--bf-radius-modal);
  background-color: color-mix(in srgb, var(--bf-info) 9%, transparent);
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
}

.bf-ordbar b {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

@media (min-width: 620px) {
  .bf-ordbar {
    flex-wrap: nowrap;
  }

  .bf-ordbar .count {
    margin-left: auto;
    white-space: nowrap;
  }
}

.bf-node .roles {
  display: flex;
  gap: 0.25rem;
}

/* LAS MARCAS POR ROL dicen qué CONCEDE un rol, y eso es un AJUSTE, no un estado
   de planta (S-8): iban en verde —todo el módulo— y en ámbar —una parte—, y en este
   sistema el ámbar dice «no puedes». Se pintan como la casilla (90.256), con la
   tinta de marca y sin semáforo, en tres grados que se leen sin el color:
     · NADA: la muesca hundida y el «·» o el «0» en gris;
     · TODO (`.on`): el velo de seleccionado —la pieza se levanta, como el segmento
       puesto— con el filete y la letra en la tinta de marca legible;
     · UNA PARTE (`.part`): la tinta de marca sobre la muesca hundida, sin levantarse.
   Lo que se lee de verdad es lo escrito —✓, el recuento o el punto—; el grado sólo
   ayuda a recorrer una columna de un vistazo (M-2). */
.bf-node .roles i {
  width: 1.25rem;
  height: 1.25rem;
  border-radius: var(--bf-radius-xs);
  font-style: normal;
  font-size: var(--bf-text-micro);
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--bf-panel-bg);
  color: var(--bf-dim);
  border: 1px solid var(--bf-border);
}

.bf-node .roles i.on {
  background-color: var(--bf-selected);
  color: var(--bf-selected-ink);
  border-color: color-mix(in srgb, var(--bf-selected-ink) 45%, transparent);
}

.bf-node .roles i.part {
  color: var(--bf-selected-ink);
  border-color: color-mix(in srgb, var(--bf-selected-ink) 45%, transparent);
}

/* ── El árbol que SÍ concede (chore 90.113) ────────────────────────────────────
   El editor de un rol necesita que cada nodo se pueda marcar, y eso pide una
   casilla DE VERDAD: sin ella conceder dependería de JavaScript, y el POST de la
   pantalla no tendría qué enviar. La casilla real va escondida —no oculta: sigue
   siendo enfocable y la anuncia el lector de pantalla— y quien se ve es la
   `.bf-box` hermana, que es la pieza que ya sabe decir concedido / efectivo / a
   medias. El estado lo pinta el propio :checked, así que al marcar cambia el
   dibujo SIN JavaScript. Mismo patrón que `.bf-seg label input` (90.070). */
.bf-node > input[type="checkbox"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  pointer-events: none;
}

/* Concedido = aparece la marca, igual que `.bf-box.on` (chore 90.256): el dibujo
   y su trazado viven en la casilla del catálogo (§ Casilla); aquí sólo se dice
   CUÁNDO se enciende. */
.bf-node > input[type="checkbox"]:checked + .bf-box::after {
  opacity: 1;
  transform: none;
}

/* El foco del teclado tiene que verse en la CASILLA DIBUJADA, porque la real no
   se ve: sin esto, recorrer el árbol con el tabulador sería a ciegas. La línea
   de tinta del anillo doble (90.147) se le pone aquí porque la del reset se la
   queda la casilla REAL, que está escondida. */
.bf-node > input[type="checkbox"]:focus-visible + .bf-box {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
  box-shadow: 0 0 0 1px var(--bf-surface-fg);
}

/* Apagada = esa fila no decide: lo concede su módulo, o el catálogo lo retiró y
   el rol no lo tenía (añadirlo sería un 409). Se sigue viendo lo que concede. */
.bf-node > input[type="checkbox"]:disabled + .bf-box {
  opacity: 0.75;
}

.bf-node > input[type="checkbox"]:disabled ~ .name,
.bf-node > input[type="checkbox"]:disabled + .bf-box {
  cursor: default;
}

/* El cuadro dibujado es también etiqueta de la casilla (chore 90.360): pulsarlo
   marca, igual que el rótulo, y por eso apunta como él. */
.bf-node .name,
.bf-node > label.bf-box {
  cursor: pointer;
}

/* El plano de la pestaña: el árbol y, al lado, lo que resulta de él. Mobile-first
   (MF-2): en el teléfono el resultado baja debajo del árbol —leer primero la
   causa y luego el efecto— y desde el corte medio se ponen en paralelo, que es
   como lo dibuja la maqueta: efecto junto a causa.

   DOS PANTALLAS lo usan y por eso el plano se escribe una vez: el editor de
   permisos de un rol (árbol + menú resultante, chore 90.117) y el árbol de
   categorías (árbol + vista previa del portal, chore 90.078). Conserva el nombre
   con el que nació —no lo miran ni el arnés ni los productos, pero renombrarlo
   sólo movería la confusión de sitio—; lo que manda es el comentario. */
.bf-role-record {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

.bf-role-tree {
  min-width: 0;
}

.bf-role-aside {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

@media (min-width: 900px) {
  .bf-role-record {
    flex-direction: row;
    align-items: flex-start;
  }

  .bf-role-tree {
    flex: 1;
  }

  .bf-role-aside {
    width: 18.75rem;
    flex: none;
  }
}

/* La VISTA PREVIA DEL MENÚ imita la barra del producto —con los tokens del
   lateral, así que el día que el lateral cambie de lámina (el contrato lo quiere
   blanco en tema claro, T3) la vista previa cambia con él sin tocar esta regla—
   porque lo que enseña no es una lista cualquiera: es lo que esa persona se va a
   encontrar al entrar. Las entradas que el rol no ve llegan del servidor con
   `hidden`, así que aparecen y desaparecen al marcar sin volver a preguntar. */
.bf-menupv {
  background: var(--bf-sidebar-bg);
  color: var(--bf-sidebar-fg);
  border-radius: var(--bf-radius-btn);
  margin: 0.75rem;
  padding: 0.75rem 0.625rem;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.bf-menupv ul {
  list-style: none;
  margin: 0;
  padding: 0;
}

.bf-menupv p {
  font-size: var(--bf-text-micro);
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--bf-sidebar-muted);
  padding: 0.5rem 0.5625rem 0.25rem;
  margin: 0;
}

.bf-menupv li li {
  display: flex;
  align-items: center;
  min-height: var(--bf-control-height);
  padding: 0 0.5625rem;
  font-size: var(--bf-sidebar-font-size);
  color: var(--bf-sidebar-fg);
}

.bf-menupv [hidden] {
  display: none;
}

/* EL EDITOR DE UN ROL POR NIVELES DE ACCESO (00.002 1.42.0). La columna de la causa pasa a tener hasta tres
   tarjetas —el árbol por niveles, los permisos especiales y lo retirado que el rol conserva— y ocupa el sitio que
   antes ocupaba el árbol solo. */
.bf-role-main {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  min-width: 0;
}

@media (min-width: 900px) {
  .bf-role-main {
    flex: 1;
  }
}

/* Cada fila con hijos lleva su «Acceso» a la derecha, con el segmentado compacto. El rótulo cede hasta 10 rem y,
   por debajo de eso, el selector baja a la línea de abajo en vez de dejar el nombre en nada (MF-1). */
.bf-access .bf-node {
  flex-wrap: wrap;
  row-gap: 0.375rem;
}

.bf-access .bf-node .name {
  flex: 1 1 10rem;
}

.bf-access .bf-node .acc {
  flex: none;
  margin-left: auto;
}

.bf-access .bf-node.head .acc-head {
  flex: none;
  margin-left: auto;
  font-size: var(--bf-text-mini);
  color: var(--bf-dim);
}

/* Las acciones que concede «Ver» lo dicen: así se sabe qué entra en «Ver» sin abrir el manifiesto. */
.bf-access .acc-read {
  flex: none;
  padding: 0 0.4rem;
  border: 1px solid var(--bf-border);
  border-radius: 999px;
  font-size: var(--bf-text-micro);
  color: var(--bf-muted);
}

/* Las claves del catálogo sólo le dicen algo a quien integra un producto: escondidas, salvo con «Claves» puesto. */
.bf-role-main .bf-node .key {
  display: none;
}

.bf-role-main:has(.bf-keys input:checked) .bf-node .key {
  display: inline;
}

.bf-keys {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  margin-left: 0.75rem;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
  cursor: pointer;
}

/* Dónde vive un permiso especial (su módulo y su recurso), detrás del nombre y más apagado. */
.bf-role-specials .bf-node .ctx {
  margin-left: 0.5rem;
  font-size: var(--bf-text-mini);
  color: var(--bf-dim);
}

.bf-role-specials > .bf-hint,
.bf-role-retired > .bf-hint {
  margin: 0;
  padding: 0 1rem 0.5rem;
}

/* ═══ 7 · Inicio (antes «Portal de servicios») ═══════════════════════════════

   Viste como la PORTADA DE HUGIN (hugin/src/front/styles/home.css): arriba el
   saludo en una pieza apenas velada con luz en la esquina, debajo una calle por
   producto con su cabecera de sección en versalitas apagadas, y las tarjetas
   con el velo, el canto y el radio de las de Hugin, en una rejilla que llena la
   vista. Todo entra escalonado con la curva de Hugin. */

/* La cabecera de las páginas de cuenta (perfil…): saludo y acciones en fila. */
.bf-portal-head {
  display: flex;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: 20px;
}

.bf-portal-head .acts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin-left: auto;
}

.bf-portal-head h1 {
  font-size: 27px;
  font-weight: 600;
  letter-spacing: -0.025em;
}

.bf-portal-head p {
  font-size: 13.5px;
  color: var(--bf-muted);
  margin-top: 5px;
}

/* EL SALUDO DEL INICIO, el `.home-hero` de Hugin: una pieza velada al 54 % con su
   canto, y en la esquina de arriba a la izquierda una luz del acento al 3 %. */
.bf-portal-head.bf-home-hero {
  align-items: center;
  gap: 1rem 1.5rem;
  margin-bottom: 1.25rem;
  padding: 1.25rem 1.5rem;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-modal);
  background-color: var(--bf-card-veil);
  background-image: radial-gradient(90% 130% at 0% 0%,
      color-mix(in srgb, var(--bf-accent) 3%, transparent) 0%, transparent 42%);
}

.bf-home-hero h1 {
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: normal;
  line-height: 1.15;
}

.bf-home-hero p {
  margin-top: 0.25rem;
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
}

.bf-home-hero .acts {
  gap: 0.5rem;
}

/* La calle: la cabecera de sección de Hugin —icono y rótulo en versalitas apagadas— con el recuento a su derecha.
   Desde la chore 90.440 la llevan los BLOQUES del Inicio (actividad, instancias…); los servicios van en pestañas. */
.bf-lane {
  margin-top: 1.5rem;
}

.bf-lane > header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.625rem;
}

.bf-lane > header h2 {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  line-height: 1;
  font-size: var(--bf-text-ui);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

.bf-lane > header h2::before {
  content: "";
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  background-color: currentColor;
  -webkit-mask: var(--bf-home-lane-mark) center / contain no-repeat;
  mask: var(--bf-home-lane-mark) center / contain no-repeat;
}

.bf-lane > header em {
  font-style: normal;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
  white-space: nowrap;
}

.bf-lane > header .rule {
  display: none;
}

/* En un teléfono la cabecera de un bloque no cabía —rótulo, resumen, flechas y «Ver todo» en un renglón— y empujaba
   el «Ver todo» 25 px fuera de la página (chore 90.622, medido a 390 px): el resumen es lo que cede, con su elipsis. */
.bf-lane > header em {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* EL INDICADOR DE POSICIÓN DEL CARRIL (chore 90.701): una barrita fina y centrada bajo las tarjetas, con un tramo que
   dice cuánto se ve y por dónde va. La pone portal.ts sólo si hay recorrido. */
.bf-rail-pos {
  position: relative;
  display: block;
  width: 4.5rem;
  height: 3px;
  margin: 0.625rem auto 0;
  border-radius: 999px;
  overflow: hidden;
  background-color: color-mix(in srgb, var(--bf-muted) 16%, transparent);
}

.bf-rail-pos[hidden] { display: none; }

.bf-rail-pos > i {
  position: absolute;
  inset-block: 0;
  left: var(--pos-x, 0%);
  width: var(--pos-w, 100%);
  border-radius: inherit;
  background-color: color-mix(in srgb, var(--bf-muted) 60%, transparent);
  transition: left 120ms linear;
}

@media (prefers-reduced-motion: reduce) {
  .bf-rail-pos > i { transition: none; }
}

:root[data-reduce~="motion"] .bf-rail-pos > i { transition: none; }

/* Arrastrando con el ratón (portal.ts): ni imán ni desplazamiento suave, que el carril siga a la mano. */
.bf-home-cards.bf-home-rail[data-dragging] {
  scroll-snap-type: none;
  scroll-behavior: auto;
  cursor: grabbing;
  user-select: none;
}

.bf-home-cards.bf-home-rail[data-dragging] a { pointer-events: none; }

.card-name {
  display: block;
  overflow: hidden;
  font-size: var(--bf-text-body, 0.875rem);
  font-weight: 600;
  line-height: 1.25;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Bloques del Inicio: la misma cabecera de calle, con su «ver todo» a la derecha. */
/* El `.home-section-link` de Hugin (chore 90.382): seminegrita tenue con su caret; al apuntar, a tinta entera,
   sin naranja. Y NADA MÁS (chore 90.413): en Hugin el cambio de tinta es seco —sin transición— y el caret no se
   mueve. */
.bf-home-block > header .more {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  margin-left: auto;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
  white-space: nowrap;
  text-decoration: none;
}

.bf-home-block > header .more > svg {
  width: 0.9rem;
  height: 0.9rem;
  flex: none;
}

.bf-home-block > header .more:hover {
  color: var(--bf-surface-fg);
  text-decoration: none;
}

/* LA ENTRADA DE HUGIN (`item-in`): sube 0,9 rem y crece un pelo, con su curva y
   escalonada — el saludo, luego cada calle y, dentro, cada tarjeta. */
@keyframes bf-item-in {
  from { opacity: 0; transform: translateY(0.9rem) scale(0.985); }
}

.bf-home-hero,
.bf-home-desk,
.bf-lane > header {
  animation: bf-item-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.bf-home-desk { animation-delay: 55ms; }
.bf-lane > header { animation-delay: 110ms; }

@media (prefers-reduced-motion: reduce) {
  .bf-home-hero,
  .bf-home-desk,
  .bf-lane > header {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-home-hero,
:root[data-reduce~="motion"] .bf-home-desk,
:root[data-reduce~="motion"] .bf-lane > header {
  animation: none;
}

/* ── INICIO CON MÁS CARTAS (chore 90.372) ─────────────────────────────────────
   Fecha y pendientes en el saludo, fila de cifras con su gráfico, carriles que
   se desplazan como el `.home-rail` de Hugin y gráficos pequeños en SVG. Todo
   con los tokens de tokens.css, así que el tema oscuro sale solo. Mobile-first:
   las rejillas son intrínsecas (auto-fit/minmax) y sólo el saludo ensancha. */

.bf-home-hero .bf-home-date {
  margin: 0 0 0.3rem;
  font-size: var(--bf-text-label);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

@media (min-width: 900px) {
  .bf-home-hero h1 { font-size: 1.75rem; }
}

/* La FILA DE CIFRAS: cartas bajas con el valor grande y su gráfico a la derecha. */
.bf-home-stats ul {
  display: grid;
  /* 9 rem de suelo (chore 90.691): con 11,5 la séptima cifra caía sola en un segundo renglón a 1440, y en el
     teléfono cada cifra ocupaba un renglón entero; ahora caben las siete en escritorio y dos por renglón en móvil. */
  grid-template-columns: repeat(auto-fit, minmax(min(9rem, 100%), 1fr));
  gap: 0.75rem;
  list-style: none;
  margin: 1rem 0 0.25rem;
  padding: 0;
}

.bf-home-stat {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  height: 100%;
  padding: 0.8rem 0.95rem;
  color: inherit;
  text-decoration: none;
  background-color: var(--bf-card-veil);
  background-image: radial-gradient(110% 140% at 100% 0%,
      color-mix(in srgb, var(--bf-muted) 8%, transparent) 0%, transparent 60%);
  /* Sin canto (chore 90.443): la cifra es una carta que abre algo y se distingue por su apuntado, no por un filete. */
  border: 1px solid transparent;
  border-radius: var(--bf-radius-modal);
  transition: transform 220ms var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

/* El apuntado de la cifra es el de la tarjeta que abre algo en Hugin (`gallery.css`, `.card[data-open]`; chore
   90.413): es una carta que lleva a su listado. Sube dos píxeles sobre una sombra que sólo cambia de opacidad, el
   velo sube un peldaño y el canto NO cambia; pulsada, se hunde en 90 ms. */
.bf-home-stat::before {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 0;
  border-radius: inherit;
  box-shadow: 0 8px 18px -10px color-mix(in srgb, var(--bf-shadow-ink) 45%, transparent);
  opacity: 0;
  transition: opacity 220ms var(--bf-ease-out);
  pointer-events: none;
}

.bf-home-stat:hover::before { opacity: 1; }

.bf-home-stat:hover {
  transform: translateY(-2px);
  background-color: var(--bf-card-veil-hover);
}

.bf-home-stat:active {
  transform: translateY(0);
  transition-duration: 90ms;
}

.bf-home-stat .txt {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.bf-home-stat .label {
  display: -webkit-box;
  overflow: hidden;
  /* Dos renglones reservados en TODAS (chore 90.691): «Actividad 24 h» se partía y bajaba su cifra respecto de
     las vecinas; con el mismo alto de rótulo, las siete cifras quedan en la misma línea sin recortar el texto. */
  min-height: calc(2 * 1.3em);
  font-size: var(--bf-text-label);
  font-weight: 600;
  line-height: 1.3;
  color: var(--bf-muted);
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}

.bf-home-stat .value {
  font-size: var(--bf-text-hero);
  font-weight: 600;
  line-height: 1.2;
  font-variant-numeric: tabular-nums;
}

.bf-home-stat.warn .value { color: var(--bf-warning-text); }
.bf-home-stat.bad .value { color: var(--bf-danger-text); }

.bf-home-stat small {
  overflow: hidden;
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-home-stat .bf-viz.ring { width: 2.75rem; height: 2.75rem; }
.bf-home-stat .bf-viz.ring { flex: none; }

/* LAS FLECHAS DEL CARRUSEL (chore 90.383): el `.rail-nav` de Hugin, en la cabecera del bloque y no flotando
   sobre las cartas. Nacen con `data-vacio` desde el servidor: sin JavaScript no se ven (el carril se recorre
   igual con rueda, arrastre o teclado) y `railWire` (portal.ts) las quita o las pone según haya recorrido. */
.bf-home-block > header .rail-nav {
  display: inline-flex;
  gap: 0.125rem;
  margin-left: auto;
}

.bf-home-block > header .rail-nav[data-vacio] { display: none; }

/* Con flechas, el `margin-left: auto` es de ellas y el «ver todo» va pegado detrás. */
.bf-home-block > header .rail-nav:not([data-vacio]) + .more { margin-left: 0; }

.rail-btn {
  display: grid;
  place-items: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
}

.rail-btn > svg { width: 0.9rem; height: 0.9rem; }
/* El apuntado de Hugin, SECO (chore 90.413): su `.rail-btn` no lleva transición. */
.rail-btn:hover:not(:disabled) { background-color: var(--bf-hover); color: var(--bf-surface-fg); }
.rail-btn:disabled { opacity: 0.4; cursor: default; }

.rail-btn:focus-visible {
  outline: 2px solid var(--bf-cta);
  outline-offset: 2px;
}

/* Los anchos del fundido son propiedades registradas para que la máscara TRANSICIONE al rodar. */
@property --fade-izq { syntax: "<length>"; inherits: false; initial-value: 0px; }
@property --fade-der { syntax: "<length>"; inherits: false; initial-value: 0px; }

/* EL CARRIL: el `.home-rail` de Hugin, una fila que se empuja con imán suave y hasta una docena de cartas.
   Sin barra a la vista: lo que queda escondido lo dicen el fundido del canto y las flechas. Sin JavaScript
   se desplaza igual (rueda, arrastre, teclado sobre la región); los fundidos quedan a cero y la fila se ve
   entera, sin máscara. Es una lista sin viñetas: nada de punto ni sangría de lista. */
.bf-home-cards.bf-home-rail {
  display: flex;
  gap: 0.75rem;
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-behavior: smooth;
  scrollbar-width: none;
  /* Los dos anchos los pone `railWire` al rodar: sólo se funde el lado que tiene algo escondido. */
  --fade-izq: 0px;
  --fade-der: 0px;
  /* El #000 de las máscaras no es un color: la máscara solo lee la opacidad, así que ni el tema ni la marca lo tocan. */
  mask-image: linear-gradient(to right,
    transparent 0, #000 var(--fade-izq), #000 calc(100% - var(--fade-der)), transparent 100%);
  -webkit-mask-image: linear-gradient(to right,
    transparent 0, #000 var(--fade-izq), #000 calc(100% - var(--fade-der)), transparent 100%);
  transition: --fade-izq var(--bf-dur-base) var(--bf-ease-out), --fade-der var(--bf-dur-base) var(--bf-ease-out);
  /* Aire para el relieve al pasar el ratón, devuelto con margen negativo (Hugin). */
  padding-block: 8px 26px;
  margin-block: calc(0.125rem - 8px) calc(0.125rem - 26px);
}

.bf-home-cards.bf-home-rail::-webkit-scrollbar { display: none; }

/* BAJO EL LATERAL (chore 90.630), el sangrado del `.home-rail` de Hugin: la caja que rueda llega a los dos cantos de
   la ventana —por la izquierda, por debajo del cristal del lateral— con margen negativo, y lo devuelve como relleno
   para que en reposo la primera carta siga en la columna; el imán cuenta con ese relleno (`scroll-padding`), o el
   carril nacería ya rodado. Las dos distancias las mide portal.ts (`bleed`): el lateral es del shell y no publica su
   ancho. Sin la medida (sin JavaScript) valen cero y el carril se queda donde estaba. */
.bf-home-cards.bf-home-rail[data-bleed] {
  margin-inline: calc(-1 * var(--bleed-l, 0px)) calc(-1 * var(--bleed-r, 0px));
  padding-inline: var(--bleed-l, 0px) var(--bleed-r, 0px);
  scroll-padding-inline: var(--bleed-l, 0px) var(--bleed-r, 0px);
}

.bf-home-rail:focus-visible {
  outline: 2px solid var(--bf-cta);
  outline-offset: 2px;
  border-radius: var(--bf-radius-btn);
}

.bf-home-rail > li {
  flex: 0 0 min(17rem, 86%);
  min-width: 0;
  scroll-snap-align: start;
}

@media (prefers-reduced-motion: reduce) {
  .bf-home-cards.bf-home-rail { scroll-behavior: auto; transition: none; }
  .bf-home-stat,
  .bf-home-stat::before { transition: none; }
  .bf-home-stat:hover,
  .bf-home-stat:active { transform: none; }
}

:root[data-reduce~="motion"] .bf-home-stat,
:root[data-reduce~="motion"] .bf-home-stat::before { transition: none; }
:root[data-reduce~="motion"] .bf-home-stat:hover,
:root[data-reduce~="motion"] .bf-home-stat:active { transform: none; }

/* ── Gráficos pequeños ─────────────────────────────────────────────────────────
   Apoyo del texto, nunca el dato a secas (M-2): van con aria-hidden. El tono
   del gráfico sigue al del estado; sin tono, el gris apagado del sistema. */
.bf-viz {
  --bf-viz-ink: color-mix(in srgb, var(--bf-muted) 70%, transparent);
  display: block;
  flex: none;
  color: var(--bf-viz-ink);
}

.bf-viz.ok { --bf-viz-ink: var(--bf-success); }
.bf-viz.warn { --bf-viz-ink: var(--bf-warning); }
.bf-viz.bad { --bf-viz-ink: var(--bf-danger); }

.bf-viz svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.bf-viz .track {
  fill: none;
  stroke: color-mix(in srgb, var(--bf-muted) 16%, transparent);
}

.bf-viz.ring .track,
.bf-viz.ring .fill {
  fill: none;
  stroke-width: 4;
}

.bf-viz.ring .fill {
  stroke: currentColor;
  stroke-linecap: round;
  animation: bf-viz-ring 900ms var(--bf-ease-out) 250ms both;
}

@keyframes bf-viz-ring {
  from { stroke-dasharray: 0 100; }
}

.bf-viz.bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.4rem;
}

.bf-viz.bar svg {
  flex: 1 1 auto;
  height: 6px;
}

.bf-viz.bar .track { fill: color-mix(in srgb, var(--bf-muted) 16%, transparent); stroke: none; }
.bf-viz.bar .fill {
  fill: currentColor;
  transform-origin: left center;
  animation: bf-viz-grow 700ms var(--bf-ease-out) 250ms both;
}

@keyframes bf-viz-grow {
  from { transform: scaleX(0); }
}

.bf-viz .lbl {
  flex: none;
  font-size: var(--bf-text-mini);
  font-weight: 600;
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-viz.bars {
  display: flex;
  align-items: flex-end;
  gap: 0.5rem;
  margin-top: 0.35rem;
}

.bf-viz.bars svg {
  flex: 1 1 auto;
  height: 1.5rem;
}

.bf-viz.bars rect.on { fill: color-mix(in srgb, var(--bf-success) 70%, transparent); }
.bf-viz.bars rect.bad { fill: var(--bf-danger); }
.bf-viz.bars rect.none { fill: color-mix(in srgb, var(--bf-muted) 18%, transparent); }

/* Entrada escalonada de lo nuevo: cifras y cartas de cada carril. */
.bf-home-stats li,
.bf-home-rail > li {
  animation: bf-item-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.bf-home-stats li:nth-child(1) { animation-delay: 80ms; }
.bf-home-stats li:nth-child(2) { animation-delay: 120ms; }
.bf-home-stats li:nth-child(3) { animation-delay: 160ms; }
.bf-home-stats li:nth-child(4) { animation-delay: 200ms; }
.bf-home-stats li:nth-child(5) { animation-delay: 240ms; }
.bf-home-stats li:nth-child(n + 6) { animation-delay: 280ms; }
.bf-home-rail > li:nth-child(1) { animation-delay: 165ms; }
.bf-home-rail > li:nth-child(2) { animation-delay: 210ms; }
.bf-home-rail > li:nth-child(3) { animation-delay: 255ms; }
.bf-home-rail > li:nth-child(4) { animation-delay: 300ms; }
.bf-home-rail > li:nth-child(5) { animation-delay: 345ms; }
.bf-home-rail > li:nth-child(n + 6) { animation-delay: 390ms; }

@media (prefers-reduced-motion: reduce) {
  .bf-home-stats li,
  .bf-home-rail > li,
  .bf-viz.ring .fill,
  .bf-viz.bar .fill {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-home-stats li,
:root[data-reduce~="motion"] .bf-home-rail > li,
:root[data-reduce~="motion"] .bf-viz.ring .fill,
:root[data-reduce~="motion"] .bf-viz.bar .fill {
  animation: none;
}

/* ── INICIO COMO EL DE HUGIN (chore 90.374) ───────────────────────────────────
   Tres piezas copiadas de Hugin con sus números, curvas y tiempos, cambiando
   sólo los tokens por los de tokens.css:
     · la CABECERA (`home.css`, `.home-hero` y `.home-health-pill`): saludo por
       hora, fecha debajo y, a la derecha, TODO lo pendiente en pastillas;
     · la MESA de accesos (`home.css`, `.home-desk`/`.home-act`): una tira
       segmentada, no una rejilla de cartas;
     · la CARTA de item (`gallery.css` + `glass.css`, la `.card[data-open]` de
       la galería de paneles): vista previa 8:5, banda de cristal flotando
       encima, apuntado en cuatro tiempos y, con puntero fino, el RELIEVE
       (`relief.ts`, portado a wwwroot/ts/ui/relief.ts).
   Las tintas de partida de Hugin son las del tema oscuro; aquí la de partida es
   la clara, así que el relieve lleva las claras por defecto y las oscuras en los
   dos interruptores del tema (data-theme y prefers-color-scheme). */

/* La cabecera: `.home-hero`. */
.bf-home-hero > .main {
  min-width: 0;
}

.bf-home-hero h1 {
  font-size: var(--bf-text-hero);
}

@media (min-width: 900px) {
  .bf-home-hero h1 { font-size: var(--bf-text-hero); }
}

.bf-home-hero .bf-home-date {
  margin: 0.25rem 0 0;
  font-size: var(--bf-text-ui);
  font-weight: 400;
  letter-spacing: normal;
  text-transform: none;
  color: var(--bf-muted);
}

.bf-home-hero-side {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin-left: auto;
}

/* Las pastillas: `.home-health-pill`. El color va SÓLO en el piloto. */
.bf-home-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 1.9rem;
  padding-inline: 0.75rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) 60%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-border) 80%, transparent);
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-home-pill > .dot {
  width: 0.45rem;
  height: 0.45rem;
  flex-shrink: 0;
  border-radius: 999px;
  background-color: var(--bf-dim);
}

.bf-home-pill[data-level="ok"] > .dot { background-color: var(--bf-success); }
.bf-home-pill[data-level="warn"] > .dot { background-color: var(--bf-warning); }

/* LA ACTIVIDAD, COMO ENLACE DE LA CABECERA (chore 90.703; antes un carril bajo los servicios): icono, rótulo y la
   cuenta de 24 h en tenue, abajo a la derecha del saludo. Tinta apagada que se aviva al apuntar, sin naranja (el
   `.home-section-link` de Hugin). En un teléfono baja al flujo, debajo de lo demás. */
.bf-home-hero-link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.5rem;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
  text-decoration: none;
  white-space: nowrap;
}

.bf-home-hero-link > svg { flex: none; width: 0.95rem; height: 0.95rem; }
.bf-home-hero-link > .n { font-weight: 500; color: var(--bf-dim, var(--bf-muted)); }
.bf-home-hero-link > .n::before { content: "·"; margin-right: 0.4rem; }
.bf-home-hero-link:hover { color: var(--bf-surface-fg); text-decoration: none; }
.bf-home-hero-link:focus-visible { outline: 2px solid var(--bf-cta); outline-offset: 2px; border-radius: 0.25rem; }

/* Desde 620 px el enlace va en la esquina de abajo y el saludo le reserva su fila: sin ella, cuando las pastillas bajan
   a su propia fila, el enlace se montaba sobre la última. */
@media (min-width: 620px) {
  .bf-home-hero:has(> .bf-home-hero-link) { padding-bottom: 2.75rem; }

  .bf-home-hero-link {
    position: absolute;
    right: var(--bf-hero-pad-x, 1.5rem);
    bottom: 1rem;
    margin-top: 0;
  }
}

/* La mesa: `.home-desk` y `.home-act`. */
.bf-home-desk {
  display: flex;
  flex-wrap: wrap;
  gap: 1px;
  overflow: hidden;
  margin: 0 0 0.5rem;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-modal);
}

.bf-home-act {
  flex: 1 1 13.5rem;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0.75rem 0.75rem 1rem;
  color: inherit;
  text-align: left;
  text-decoration: none;
  background-color: var(--bf-card-veil);
  cursor: pointer;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

/* El apuntado de `.home-act:hover` (Hugin, home.css): SÓLO el peldaño de velo 54 → 62 %, sin subrayado. Lo de
   `text-decoration` va aquí y no en la regla de arriba porque la de los enlaces (`a:hover`) le gana en
   especificidad a una clase sola, y el mando salía subrayado al apuntarlo (chore 90.495). */
.bf-home-act:hover {
  background-color: var(--bf-card-veil-hover);
  text-decoration: none;
}

/* El foco de Hugin (`:focus-visible` de base.css): aro de 2 px del naranja de marca por fuera, que ENTRA cayendo
   desde 7 px (`focus-in`), y sin el canto de contraste que el catálogo añade al aro. */
.bf-home-act:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
  box-shadow: none;
  animation: bf-home-focus-in var(--bf-dur-base) var(--bf-ease-out);
}

@keyframes bf-home-focus-in {
  from {
    outline-color: transparent;
    outline-offset: 7px;
  }
}

.bf-home-act .ico {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  border-radius: var(--bf-radius-btn);
  background-color: color-mix(in srgb, var(--bf-accent) 14%, transparent);
  color: var(--bf-accent);
}

.bf-home-act .ico svg { width: 1rem; height: 1rem; }

.bf-home-act .txt {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
}

.bf-home-act .title { font-size: var(--bf-text-ui); font-weight: 600; }

.bf-home-act .state {
  overflow: hidden;
  font-size: var(--bf-text-mini);
  line-height: 1.35;
  color: var(--bf-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-home-act .caret {
  width: 0.85rem;
  height: 0.85rem;
  flex-shrink: 0;
  margin-left: auto;
  rotate: 90deg;
  color: var(--bf-muted);
  opacity: 0.5;
  transition: opacity var(--bf-dur-base) var(--bf-ease-out), color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-home-act:hover .caret {
  color: var(--bf-accent);
  opacity: 1;
}

/* La carta: `.card` (glass.css) + `.card[data-open]` (gallery.css). */
.bf-home-card {
  --card-edge: transparent;

  position: relative;
  display: block;
  min-width: 0;
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  background-color: var(--bf-card-veil);
  border: 0;
  border-radius: var(--bf-radius-modal);
  transition: transform 220ms var(--bf-ease-out),
    box-shadow var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-home-card:focus-visible {
  outline: 2px solid var(--bf-cta);
  outline-offset: 2px;
}

.bf-home-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  /* La tinta de sombra de Hugin (`--shadow-ink`) es negra en los DOS temas (chore 90.413). */
  box-shadow: 0 8px 18px -10px color-mix(in srgb, var(--bf-shadow-ink) 45%, transparent);
  opacity: 0;
  transition: opacity 220ms var(--bf-ease-out);
  pointer-events: none;
}

.bf-home-card:hover::before { opacity: 1; }

.bf-home-card:hover {
  transform: translateY(-2px);
  background-color: var(--bf-card-veil-hover);
}

.bf-home-card::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--card-edge), var(--bf-glass-bevel);
  transition: box-shadow var(--bf-dur-base) var(--bf-ease-out);
  pointer-events: none;
}

.bf-home-card:active {
  transform: translateY(0);
  transition-duration: 90ms;
}

/* La VISTA PREVIA: `.card-preview`, 8:5 y la banda flotando encima. Lo que aquí
   hace de miniatura es el dato del item (cifra, estado, gráfico); el zoom lento
   del apuntado lo hace su contenido, como la captura en Hugin. */
.bf-home-card .card-preview {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.375rem;
  aspect-ratio: 8 / 5;
  margin-bottom: 0;
  padding: 0.875rem 0.875rem 3.4rem;
  overflow: hidden;
}

.bf-home-card .card-preview > * {
  transition: transform 700ms cubic-bezier(0.16, 0.7, 0.25, 1);
}

/* `will-change` sólo mientras se apunta, como la captura de Hugin: sube lo que crece a su capa para que el zoom
   no repinte la carta, sin dejar una capa por carta en memoria. */
.bf-home-card:hover .card-preview > * {
  transform: scale(1.04);
  will-change: transform;
}

.bf-home-card .card-preview .glyph {
  position: absolute;
  top: 0.75rem;
  right: 0.875rem;
  color: color-mix(in srgb, var(--bf-muted) 55%, transparent);
}

.bf-home-card .card-preview .glyph svg {
  width: 1.5rem;
  height: 1.5rem;
}

/* LA BALDOSA DEL PRODUCTO EN MEDIO (chore 90.635): la carta de estado de una instancia deja el icono genérico de la
   esquina y lleva, centrada en la vista previa, la de su producto en su tono —la paleta de Productos por `data-hue`,
   la de las tarjetas de los servicios—; sin icono, sus iniciales. El estado queda arriba y la tendencia abajo. */
.bf-home-card .card-preview .mark {
  --mark-hue: var(--bf-hue-0);
  display: grid;
  flex: none;
  place-items: center;
  align-self: center;
  width: 2.75rem;
  height: 2.75rem;
  margin-block: auto;
  border-radius: 0.9rem;
  background-color: color-mix(in srgb, var(--mark-hue) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--mark-hue) 28%, transparent);
  color: color-mix(in srgb, var(--mark-hue) 80%, var(--bf-surface-fg));
  font-size: 1.2rem;
  font-weight: 700;
  line-height: 1;
}

.bf-home-card .card-preview .mark svg {
  width: 1.5rem;
  height: 1.5rem;
}

.bf-home-card .card-preview .mark[data-hue="1"] { --mark-hue: var(--bf-hue-1); }
.bf-home-card .card-preview .mark[data-hue="2"] { --mark-hue: var(--bf-hue-2); }
.bf-home-card .card-preview .mark[data-hue="3"] { --mark-hue: var(--bf-hue-3); }
.bf-home-card .card-preview .mark[data-hue="4"] { --mark-hue: var(--bf-hue-4); }
.bf-home-card .card-preview .mark[data-hue="5"] { --mark-hue: var(--bf-hue-5); }
.bf-home-card .card-preview .mark[data-hue="6"] { --mark-hue: var(--bf-hue-6); }
.bf-home-card .card-preview .mark[data-hue="7"] { --mark-hue: var(--bf-hue-7); }
.bf-home-card .card-preview .mark[data-hue="8"] { --mark-hue: var(--bf-hue-8); }

/* Para que quepan estado, baldosa y tendencia, la vista previa de estas cartas es algo más alta —todas las del carril
   lo son, así que la fila no se descuadra— y el estado va en PASTILLA en todos sus tonos, como en las tarjetas de los
   servicios (el «sin comprobar» ya lo era). */
.bf-home-card .card-preview.marked {
  aspect-ratio: 4 / 3;
}

.bf-home-card .card-preview.marked .figure {
  gap: 0.375rem;
  padding: 0.25rem 0.625rem 0.25rem 0.5rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, currentColor 10%, transparent);
  font-size: var(--bf-text-label);
  font-weight: 600;
}

.bf-home-card .figure {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding-right: 2rem;
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.15;
  font-variant-numeric: tabular-nums;
  transform-origin: left center;
}

.bf-home-card .caption {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  transform-origin: left center;
}

.bf-home-card .ok .figure .bf-led { background: var(--bf-success); }

/* SIN COMPROBAR (chore 90.445): «no lo sé todavía» —nadie ha sondeado— no es una cifra ni un estado afirmado, así que
   no va en grande: una pastilla apagada con el piloto HUECO en el gris de apagado. El mismo piloto lleva la pastilla
   de la tarjeta de servicio. */
.bf-home-card .idle .figure {
  gap: 0.375rem;
  padding: 0.25rem 0.625rem 0.25rem 0.5rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 12%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  font-weight: 600;
}

.bf-home-card .idle .figure .bf-led,
.bf-pill.idle > .bf-led {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px var(--bf-off);
}
.bf-home-card .warn .figure { color: var(--bf-warning-text); }
.bf-home-card .warn .figure .bf-led { background: var(--bf-warning); }
.bf-home-card .bad .figure { color: var(--bf-danger-text); }
.bf-home-card .bad .figure .bf-led { background: var(--bf-danger); }

/* EL CUERPO: `.card-body`, de cristal y flotando sobre el canto inferior. */
.bf-home-card .card-body {
  position: absolute;
  inset: auto 0 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0;
  padding: 0.25rem 0.75rem;
  border-top: 1px solid color-mix(in srgb, var(--bf-border) 70%, transparent);
  background-color: color-mix(in srgb, var(--bf-card-bg) 62%, transparent);
  backdrop-filter: blur(12px) saturate(1.3);
  -webkit-backdrop-filter: blur(12px) saturate(1.3);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-home-card .card-body { background-color: color-mix(in srgb, var(--bf-card-bg) 92%, transparent); }
}

.bf-home-card .card-name {
  font-size: var(--bf-text-body);
  line-height: 1.25;
  font-weight: 600;
}

.bf-home-card .card-foot {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  margin-top: 0.25rem;
  font-size: var(--bf-text-mini);
  line-height: 1.3;
  color: var(--bf-muted);
}

.bf-home-card .card-foot .txt {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-home-card .card-foot .badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.25rem;
  margin-left: auto;
  min-width: 1rem;
  height: 1rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  font-size: var(--bf-text-micro);
  font-weight: 600;
  background: color-mix(in srgb, var(--bf-muted) 14%, transparent);
}

/* ── LA CARTA DE LICENCIA (chore 90.621) ─────────────────────────────────────────────────────────────────────────
   Antes la carta genérica: «364 días» y una llave sobre una vista vacía. Ahora el lenguaje de las tarjetas de Hugin
   de la casa (.bf-hcard) y de la ficha de licencia vestida (90.596), compacto porque vive en un carril:
     · el TONO del producto (--lic-hue) es la paleta de iniciales de Productos (AdminPieces.ColorFor), por índice en
       `data-hue` —el Inicio no pinta `style=`—; tiñe la baldosa y un halo leve en la esquina;
     · el ESTADO (--lic-tone) manda en la cifra, el medidor y la pastilla: tono + LED + texto (M-2);
     · la vista no es 8:5 sino lo que pide su contenido, y la banda va en el flujo, no flotando: así cabe más de una
       en el ancho de un teléfono y nada se monta. */
.bf-lic {
  --lic-hue: var(--bf-hue-0);
  --lic-tone: var(--bf-success);
  --lic-ink: var(--bf-success-text, var(--bf-success));
}

/* La esquina inferior derecha es la de la CARTA ENTERA (chore 90.712, corrige la 90.702): la carta conserva su radio
   y lo hereda solo la pieza de abajo (la banda) con sus capas; la vista de arriba acaba en ÁNGULO, porque su esquina
   inferior derecha cae a media carta, sobre el filete, y redondeada se leía como un mordisco. */
.bf-home-card.bf-lic[data-relief] > .card-body,
.bf-home-card.bf-lic[data-relief] > .card-body::before,
.bf-home-card.bf-lic[data-relief] > .card-body::after {
  border-bottom-right-radius: inherit;
}

/* Con la clase y el atributo del relieve: la regla de `.bf-home-card[data-relief] > .card-preview` le pone radio y
   pesa más que una de solo `.bf-lic`. */
.bf-home-card.bf-lic[data-relief] > .card-preview,
.bf-home-card.bf-lic[data-relief] > .card-preview::before,
.bf-home-card.bf-lic[data-relief] > .card-preview::after,
.bf-home-card.bf-lic > .card-preview,
.bf-home-card.bf-lic > .card-preview::before,
.bf-home-card.bf-lic > .card-preview::after {
  border-bottom-right-radius: 0;
  border-bottom-left-radius: 0;
}

.bf-lic[data-hue="1"] { --lic-hue: var(--bf-hue-1); }
.bf-lic[data-hue="2"] { --lic-hue: var(--bf-hue-2); }
.bf-lic[data-hue="3"] { --lic-hue: var(--bf-hue-3); }
.bf-lic[data-hue="4"] { --lic-hue: var(--bf-hue-4); }
.bf-lic[data-hue="5"] { --lic-hue: var(--bf-hue-5); }
.bf-lic[data-hue="6"] { --lic-hue: var(--bf-hue-6); }
.bf-lic[data-hue="7"] { --lic-hue: var(--bf-hue-7); }
.bf-lic[data-hue="8"] { --lic-hue: var(--bf-hue-8); }

.bf-lic.warn {
  --lic-tone: var(--bf-warning);
  --lic-ink: var(--bf-warning-text);
}

.bf-lic.bad {
  --lic-tone: var(--bf-danger);
  --lic-ink: var(--bf-danger-text);
}

/* La carta es un ancla: sin esto el `a:hover` de la base (0,1,1) le subrayaba todo el texto al apuntarla. */
.bf-home-card:hover {
  text-decoration: none;
}

.bf-home-block[data-block="licenses"] > header h2::before {
  --bf-home-lane-mark: var(--bf-home-license-mark);
}

.bf-home-card.bf-lic {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.bf-home-card.bf-lic .card-preview {
  flex: 1;
  aspect-ratio: auto;
  gap: 0.625rem;
  align-items: stretch;
  padding: 0.75rem 0.875rem 0.875rem;
  isolation: isolate;
}

/* El halo: el tono del producto arriba a la izquierda y el del estado abajo a la derecha, muy bajos. */
.bf-home-card.bf-lic .card-preview::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(70% 90% at 0% 0%, color-mix(in srgb, var(--lic-hue) 14%, transparent), transparent 70%),
    radial-gradient(60% 80% at 100% 100%, color-mix(in srgb, var(--lic-tone) 9%, transparent), transparent 70%);
  pointer-events: none;
}

.bf-lic .lic-head {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
}

/* La baldosa del producto: la de .bf-hcard (tono al 20 %, canto al 26 %, tinta del tono), en pequeño. */
.bf-lic .tile {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.875rem;
  height: 1.875rem;
  border-radius: 0.6rem;
  background: color-mix(in srgb, var(--lic-hue) 20%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lic-hue) 26%, transparent);
  color: color-mix(in srgb, var(--lic-hue) 70%, var(--bf-surface-fg));
  font-size: var(--bf-text-mini);
  font-weight: 700;
}

.bf-lic .tile svg {
  width: 1rem;
  height: 1rem;
}

.bf-lic .prod {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-lic .lic-head .bf-pill {
  flex: none;
}

/* La cuenta atrás en grande, su unidad y el sello de fondo. */
.bf-lic .lic-days {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.375rem;
  min-height: 2.25rem;
  padding-right: 2.5rem;
  transform-origin: left center;
}

.bf-lic .lic-days .n {
  font-size: 1.75rem;
  font-weight: 650;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--lic-ink);
  font-variant-numeric: tabular-nums;
}

.bf-lic.ok .lic-days .n {
  color: var(--bf-surface-fg);
}

.bf-lic .lic-days .n.word {
  font-size: 1.25rem;
}

.bf-lic .lic-days .u {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-lic .lic-days .seal {
  position: absolute;
  right: 0;
  top: 50%;
  translate: 0 -50%;
  color: color-mix(in srgb, var(--lic-tone) 55%, var(--bf-muted));
  opacity: 0.75;
}

.bf-lic .lic-days .seal svg {
  width: 1.75rem;
  height: 1.75rem;
}

/* El medidor de días: pista neutra y relleno del estado, con su punta luminosa. */
.bf-lic .lic-meter {
  display: block;
  height: 6px;
  transform-origin: left center;
}

.bf-lic .lic-meter svg,
.bf-lic .use svg {
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
}

.bf-lic .track {
  fill: color-mix(in srgb, var(--bf-muted) 18%, transparent);
}

.bf-lic .lic-meter .fill {
  fill: var(--lic-tone);
  filter: drop-shadow(0 0 3px color-mix(in srgb, var(--lic-tone) 45%, transparent));
}

/* La banda: en el flujo, esmerilada como la de las demás cartas. */
.bf-home-card.bf-lic .card-body {
  position: relative;
  inset: auto;
  gap: 0.2rem;
  padding: 0.5rem 0.875rem 0.625rem;
}

.bf-lic .lic-foot {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  font-size: var(--bf-text-mini);
  line-height: 1.3;
  color: var(--bf-muted);
}

.bf-lic .lic-foot .when {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-lic .use {
  display: inline-grid;
  grid-template-columns: auto auto;
  align-items: center;
  column-gap: 0.3rem;
  row-gap: 0.15rem;
  flex: none;
  white-space: nowrap;
}

.bf-lic .use b {
  font-weight: 650;
  color: var(--bf-surface-fg);
  font-variant-numeric: tabular-nums;
}

.bf-lic .use.warn b { color: var(--bf-warning-text); }
.bf-lic .use.bad b { color: var(--bf-danger-text); }

.bf-lic .use > svg {
  grid-column: 1 / -1;
  width: 4.5rem;
  height: 4px;
}

.bf-lic .use .fill { fill: var(--bf-success); }
.bf-lic .use.warn .fill { fill: var(--bf-warning); }
.bf-lic .use.bad .fill { fill: var(--bf-danger); }

.bf-lic .use.none {
  display: inline-flex;
  gap: 0.25rem;
}

.bf-lic .use.none > svg {
  width: 0.85rem;
  height: 0.85rem;
}

.bf-lic .lic-foot .badge {
  flex: none;
  display: inline-grid;
  place-items: center;
  min-width: 1rem;
  height: 1rem;
  padding: 0 0.25rem;
  border-radius: 999px;
  font-size: var(--bf-text-micro);
  font-weight: 700;
  color: var(--bf-warning-text);
  background: color-mix(in srgb, var(--bf-warning) 18%, transparent);
}

/* La cifra «Licencias por atender» con su certificado, discreto, donde las otras llevan su gráfico. */
.bf-home-stat .bf-stat-mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.75rem;
  color: color-mix(in srgb, var(--bf-muted) 80%, transparent);
  background: color-mix(in srgb, var(--bf-muted) 10%, transparent);
}

.bf-home-stat.warn .bf-stat-mark {
  color: var(--bf-warning-text);
  background: color-mix(in srgb, var(--bf-warning) 14%, transparent);
}

.bf-home-stat.bad .bf-stat-mark {
  color: var(--bf-danger-text);
  background: color-mix(in srgb, var(--bf-danger) 14%, transparent);
}

.bf-home-stat .bf-stat-mark svg {
  width: 1.35rem;
  height: 1.35rem;
}

/* ── Relieve de la carta del Inicio: la receta de Vor que porta Hugin
   (`gallery.css`, «Y la tarjeta tiene relieve»). Solo en la carta que
   `armRelief` ha armado: con el dedo o con menos movimiento no se arma.
   Desde la chore 90.463 lo llevan también las tarjetas de Hugin de Productos
   e Instancias (.bf-hcard, con su vista y su banda como .card-preview y
   .card-body): el «hover de cristal» de las cards de Hugin.
   Desde la chore 90.607 el brillo, el halo, la pasada y el filete van un quinto
   más bajos, en los dos temas: se leía más el reflejo que la propia tarjeta. */
.bf-home-card[data-relief],
.bf-hcard[data-relief] {
  --settle: 600ms cubic-bezier(0.22, 1, 0.36, 1);
  --floor: var(--bf-relief-floor);
  /* EN CLARO (chore 90.654; sin naranja desde la 90.657): la luz es BLANCA pero más opaca que la del oscuro, para
     que se lea por contraste de luz sobre el papel cálido: un reflejo que sigue al ratón, un halo, una pasada y un
     canto blanco que se enciende. Y la carta SUBE (`--lift`) en vez de echar
     sombra: la 90.639 quitó las caídas y no vuelven. El oscuro redeclara todo esto y queda como estaba. */
  --lit: var(--bf-relief-light);
  --rim-c: var(--bf-relief-light);
  --glint: 0.62;
  --halo: 0.2;
  --shade: 0.07;
  --rim: 1;
  --sheen: 0.4;
  --lift: -3px;

  overflow: visible;
}

.bf-home-card[data-relief],
.bf-hcard[data-relief] {
  --card-veil: var(--bf-card-veil);

  background: none;
  transition: transform var(--settle), translate var(--settle);
}

:root[data-theme="dark"] .bf-home-card[data-relief],
:root[data-theme="dark"] .bf-hcard[data-relief] {
  --floor: var(--bf-relief-floor);
  --lit: var(--bf-relief-light);
  --rim-c: var(--bf-relief-light);
  --lift: 0px;
  --glint: 0.04;
  --halo: 0.015;
  --shade: 0.1;
  --rim: 0.18;
  --sheen: 0.045;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-home-card[data-relief],
  :root:not([data-theme="light"]) .bf-hcard[data-relief] {
    --floor: var(--bf-relief-floor);
    --lit: var(--bf-relief-light);
    --rim-c: var(--bf-relief-light);
    --lift: 0px;
    --glint: 0.04;
    --halo: 0.015;
    --shade: 0.1;
    --rim: 0.18;
    --sheen: 0.045;
  }
}

.bf-home-card[data-relief]:hover,
.bf-hcard[data-relief]:hover {
  --settle: 280ms var(--bf-ease-out);
  --card-veil: var(--bf-card-veil-hover);
  background: none;
  translate: 0 var(--lift, 0px);
}

.bf-home-card[data-relief]:hover:active,
.bf-hcard[data-relief]:hover:active { translate: none; }

.bf-home-card[data-relief][data-moving]:hover,
.bf-hcard[data-relief][data-moving]:hover {
  --tilt: perspective(900px) rotateX(calc(var(--dy, 0) * 2deg)) rotateY(calc(var(--dx, 0) * -2deg));
  --sweep: calc((var(--dx, 0) + var(--dy, 0) * 0.5) / 1.5);
}

.bf-home-card[data-relief]:active,
.bf-hcard[data-relief]:active {
  --settle: 90ms var(--bf-ease-out);

  transition-duration: 90ms;
}

.bf-home-card[data-relief] > .card-preview,
.bf-hcard[data-relief] > .card-preview {
  border-radius: inherit;
  background-color: var(--card-veil);
  isolation: isolate;
  transform: var(--tilt, none);
  transition: transform var(--settle),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-home-card[data-relief] > .card-body,
.bf-hcard[data-relief] > .card-body {
  border-bottom-left-radius: inherit;
  border-bottom-right-radius: inherit;
  transform: var(--tilt, none);
  transform-origin: 50% calc(var(--pivot-y, 50%) + 1px);
  transition: transform var(--settle);
}

.bf-home-card[data-relief] > .card-preview > *,
.bf-hcard[data-relief] > .card-preview > * { --z: 22; }
.bf-home-card[data-relief] > .card-body > *,
.bf-hcard[data-relief] > .card-body > * { --z: 34; }

.bf-home-card[data-relief] > .card-body > *,
.bf-hcard[data-relief] > .card-body > *,
.bf-home-card[data-relief] > .card-preview > *,
.bf-hcard[data-relief] > .card-preview > * {
  transition: translate var(--settle),
    transform 700ms cubic-bezier(0.16, 0.7, 0.25, 1),
    box-shadow 220ms var(--bf-ease-out);
}

.bf-home-card[data-relief]:hover > .card-preview > *,
.bf-hcard[data-relief]:hover > .card-preview > *,
.bf-home-card[data-relief]:hover > .card-body > *,
.bf-hcard[data-relief]:hover > .card-body > * {
  translate:
    round(calc(var(--dx, 0) * var(--z) * -0.035px), 1px)
    round(calc(var(--dy, 0) * var(--z) * -0.035px), 1px);
}

.bf-home-card[data-relief][data-moving]:hover > .card-preview > *,
.bf-hcard[data-relief][data-moving]:hover > .card-preview > *,
.bf-home-card[data-relief][data-moving]:hover > .card-body > *,
.bf-hcard[data-relief][data-moving]:hover > .card-body > * {
  translate: calc(var(--dx, 0) * var(--z) * -0.035px) calc(var(--dy, 0) * var(--z) * -0.035px);
}

.bf-home-card[data-relief]:hover:active > .card-preview > *,
.bf-hcard[data-relief]:hover:active > .card-preview > *,
.bf-home-card[data-relief]:hover:active > .card-body > *,
.bf-hcard[data-relief]:hover:active > .card-body > * {
  translate:
    round(calc(var(--dx, 0) * var(--z) * -0.01px), 1px)
    round(calc(var(--dy, 0) * var(--z) * -0.01px), 1px);
}

.bf-home-card[data-relief]::before,
.bf-hcard[data-relief]::before {
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--floor) 10%, transparent),
    0 8px 16px -6px color-mix(in srgb, var(--floor) 16%, transparent),
    0 24px 40px -18px color-mix(in srgb, var(--floor) 36%, transparent);
  translate: calc(var(--dx, 0) * -3px) calc(var(--dy, 0) * -2px + 2px);
  transform: var(--tilt, none);
  transition:
    opacity 260ms var(--bf-ease-out),
    translate var(--settle),
    transform var(--settle);
}

.bf-home-card[data-relief] > :is(.card-preview, .card-body)::after,
.bf-hcard[data-relief] > :is(.card-preview, .card-body)::after {
  --spot: calc(var(--lx, 50%) - var(--ox, 0px)) calc(var(--ly, 0%) - var(--oy, 0px));

  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background:
    linear-gradient(105deg,
        color-mix(in srgb, var(--lit) 0%, transparent) 40%,
        color-mix(in srgb, var(--lit) calc(var(--sheen) * 100%), transparent) 48%,
        color-mix(in srgb, var(--lit) calc(var(--sheen) * 0.4 * 100%), transparent) 53%,
        color-mix(in srgb, var(--lit) 0%, transparent) 61%)
        calc(var(--cw, 0px) * (-0.5 - var(--sweep, 0) * 0.6) - var(--ox, 0px)) calc(0px - var(--oy, 0px))
        / calc(var(--cw, 0px) * 2) var(--ch, 0px) no-repeat,
    radial-gradient(circle 6rem at var(--spot),
        color-mix(in srgb, var(--lit) calc(var(--glint) * 100%), transparent) 0%,
        color-mix(in srgb, var(--lit) calc(var(--glint) * 0.66 * 100%), transparent) 18%,
        color-mix(in srgb, var(--lit) calc(var(--glint) * 0.34 * 100%), transparent) 40%,
        color-mix(in srgb, var(--lit) calc(var(--glint) * 0.12 * 100%), transparent) 66%,
        color-mix(in srgb, var(--lit) 0%, transparent) 100%),
    radial-gradient(circle 20rem at var(--spot),
        color-mix(in srgb, var(--lit) calc(var(--halo) * 100%), transparent) 0%,
        color-mix(in srgb, var(--lit) calc(var(--halo) * 0.4 * 100%), transparent) 45%,
        color-mix(in srgb, var(--lit) 0%, transparent) 85%),
    linear-gradient(var(--away, 180deg),
        color-mix(in srgb, var(--floor) 0%, transparent) 45%,
        color-mix(in srgb, var(--floor) calc(var(--shade) * var(--lean, 0) * 100%), transparent) 100%);
  opacity: 0;
  transition:
    opacity 320ms var(--bf-ease-out),
    background-position var(--settle);
  pointer-events: none;
}

@property --rim-on {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

.bf-home-card[data-relief]::after,
.bf-hcard[data-relief]::after {
  padding: 1px;
  background: radial-gradient(circle 8rem at var(--lx, 50%) var(--ly, 0%),
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 100%), transparent) 0%,
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 0.4 * 100%), transparent) 40%,
      color-mix(in srgb, var(--rim-c) 0%, transparent) 100%);
  /* La máscara solo lee la opacidad: su #000 no es un color del tema. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  transform: var(--tilt, none);
  transition:
    box-shadow var(--bf-dur-base) var(--bf-ease-out),
    --rim-on 320ms var(--bf-ease-out),
    transform var(--settle);
}

.bf-home-card[data-relief]:hover::after,
.bf-hcard[data-relief]:hover::after { --rim-on: 1; }

.bf-home-card[data-relief]:hover > :is(.card-preview, .card-body)::after,
.bf-hcard[data-relief]:hover > :is(.card-preview, .card-body)::after { opacity: 1; }

/* El carril entra por su eje, como el `.home-rail` de la portada de Hugin. */
@supports (animation-timeline: view()) {
  .bf-home-rail > li {
    animation: bf-item-in 520ms cubic-bezier(0.16, 1, 0.3, 1) both;
    animation-timeline: view(inline);
    animation-range: entry 0% entry 90%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bf-home-card,
  .bf-home-card::before,
  .bf-home-card .card-preview > *,
  .bf-home-act,
  .bf-home-act .caret { transition: none; }
  .bf-home-card:hover,
  .bf-home-card:hover .card-preview > * { transform: none; }
  .bf-home-rail > li { animation: none; }
}

:root[data-reduce~="motion"] .bf-home-card,
:root[data-reduce~="motion"] .bf-home-card::before,
:root[data-reduce~="motion"] .bf-home-card .card-preview > *,
:root[data-reduce~="motion"] .bf-home-act,
:root[data-reduce~="motion"] .bf-home-act .caret { transition: none; }
:root[data-reduce~="motion"] .bf-home-card:hover,
:root[data-reduce~="motion"] .bf-home-card:hover .card-preview > * { transform: none; }
:root[data-reduce~="motion"] .bf-home-rail > li { animation: none; }
:root[data-reduce~="motion"] .bf-home-cards.bf-home-rail { scroll-behavior: auto; transition: none; }

/* LAS TARJETAS DEL INICIO, SIN SOMBRA (chore 90.636): ni la de reposo ni la que llega al apuntar, en los dos temas.
   La caída vive en el `::before` de cada carta (cifras, servicios, bloques, licencias y actividad, también la del
   relieve); se apaga ahí. Se quedan el filete y el bisel interior (`::after`, `inset`), para que no pierdan el canto.
   Sólo estas clases: son del Inicio, el resto de la app no cambia. La fila de actividad, tras su propia regla. */
.bf-home-stat::before,
.bf-home-card::before,
.bf-home-card[data-relief]::before,
/* Y en toda la app (chore 90.639): las tarjetas de admin y de cuenta (`.bf-hcard`, con o sin relieve). Las `.bf-card`
   lo hacen por sus tokens (`--bf-card-glass-drop`, `--bf-card-lift`); menús, diálogos y avisos conservan la suya. */
.bf-hcard::before,
.bf-hcard[data-relief]::before {
  box-shadow: none;
}

/* ═══ 8 · Ficha: carril de sección (M-8) ═════════════════════════════════════
   La leyenda sale de la tarjeta a un carril propio a su izquierda; la tarjeta se
   queda sólo con campos. Receta portada de 27.103/27.104. Hasta la chore 90.261 la
   tarjeta se teñía (.bf-sect) para que el campo fuera un pozo (el M-6 de la
   maqueta); con el campo de velo y filete, lo dice el filete (ver .bf-sect). */

/* ── Cabecera de expediente (chore 90.117) ─────────────────────────────────────
   Identidad del registro + TODAS sus acciones, FIJA en todas las pestañas: quién
   es esto, en qué estado está y qué se puede hacer con ello. Va dentro de una
   `.bf-card`, bajo las `.bf-tabs` y encima del formulario, y la usan los cinco
   expedientes de la maqueta de gestión (persona, rol, instancia, producto, IdP).

   Que sea UNA pieza es el convenio, no una comodidad: ninguna acción del registro
   puede depender de por qué pestaña se pasee el usuario. La maqueta la dibuja con
   `style=` en cada pantalla —allí no hay hoja que compartir—; aquí tiene nombre,
   porque si no lo tuviera cada una de las nueve familias la volvería a escribir a
   su manera.

   Mobile-first (MF-2): NACE en dos renglones —a 360 px la identidad se queda con
   el ancho y las acciones caen debajo— y se pone en línea desde el corte medio. */
.bf-rechead {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px 14px;
  padding: 11px 16px;
}

/* La identidad se encoge (el nombre puede ser largo); la marca y las acciones no. */
.bf-rechead .id {
  flex: 1 1 200px;
  min-width: 0;
}

/* Nombre, clave y estado en el MISMO renglón mientras quepan: son la respuesta a
   «¿qué es esto y cómo está?», y separarlos obliga a leer dos veces. */
.bf-rechead .line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 9px;
}

.bf-rechead .line b {
  font-size: 15px;
  font-weight: 600;
  overflow-wrap: anywhere;
}

.bf-rechead .sub {
  font-size: 12px;
  color: var(--bf-dim);
  margin-top: 2px;
  overflow-wrap: anywhere;
}

/* Las acciones envuelven antes que desbordar (MF-5). En compacto abren renglón
   propio y se leen de izquierda a derecha, como se leería una barra. */
.bf-rechead .acts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  flex: none;
  width: 100%;
}

@media (min-width: 620px) {
  .bf-rechead .acts {
    width: auto;
    justify-content: flex-end;
  }
}

/* EL CARRIL DE FICHA, RE-VESTIDO en la chore 90.261 (contrato de diseño §3, «piezas
   que solo tiene Bifrost»). M-8 sigue entero: la leyenda fuera de la tarjeta, en su
   carril, y la tarjeta sólo con campos. Cambian la métrica —en rem, y el carril con
   ella: 196 px de ancho fijo para un rótulo que crece con la raíz lo partía en tres
   renglones en un monitor de sala— y los tamaños, que pasan a la escala (§2.5).
   Y la tarjeta de sección deja de TEÑIRSE (abajo). */
.bf-form {
  display: flex;
  flex-direction: column;
  gap: 1.125rem;
}

/* Mobile-first: en compacto el carril de leyenda no cabe al lado del pozo
   (196 px de 360 son la mitad de la pantalla) y pasa ENCIMA de su tarjeta;
   desde 620 (MF-3) vuelve el carril lateral de la maqueta. */
.bf-form-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.75rem;
  align-items: start;
}

@media (min-width: 620px) {
  .bf-form-row {
    grid-template-columns: 12.25rem minmax(0, 1fr);
    gap: 1.25rem;
  }
}

.bf-frail {
  display: flex;
  gap: 0.6875rem;
  align-items: flex-start;
  padding-top: 0.375rem;
}

/* La ficha del carril: la MARCA de la sección, en la tinta de marca sobre su tinte,
   como la marca del vacío (90.257). Dice de qué va la sección, no un estado. */
.bf-frail .fic {
  width: 2.125rem;
  height: 2.125rem;
  border-radius: var(--bf-radius-btn);
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--bf-text-body);
  font-style: normal;
  font-weight: 600;
  background: color-mix(in srgb, var(--bf-accent) 13%, transparent);
  color: var(--bf-accent-text);
}

.bf-frail h3 {
  font-size: var(--bf-text-body);
  font-weight: 600;
  line-height: 1.3;
}

.bf-frail p {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  margin-top: 0.1875rem;
  line-height: 1.4;
}

/* LA TARJETA DE SECCIÓN YA NO SE TIÑE (chore 90.261). Bajaba al hundido para que el
   campo blanco se leyera como un POZO (el M-6 de la maqueta). Ese M-6 está reescrito:
   el campo es un velo con filete de mando a 3:1 (90.255), y lo que dice «aquí se
   escribe» es ese filete, no un pozo. Teñida, la sección era la única tarjeta de la
   casa que no era un velo (S-3): una bandeja gris en una pantalla de tarjetas
   translúcidas. Se queda con el velo de `.bf-card`, que es la clase que la acompaña
   siempre; la clase sigue siendo la que dice «tarjeta de sección», y sus reglas de
   dentro (`.bf-sect > .in`) siguen más abajo. */

/* Dos campos por renglón dentro de una sección (chore 90.076): es lo que la
   maqueta dibuja en la ficha de instancia —Nombre/Producto, Categoría/Versión— y
   lo que hasta ahora cada pantalla habría resuelto con un `style=` propio.
   Mobile-first (MF-2): NACE en una columna y se parte en dos desde el corte medio,
   con minmax(0,1fr) para que una URL larga no ensanche su columna (MF-5). */
.bf-grid2 {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0.875rem;
}

@media (min-width: 620px) {
  .bf-grid2 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* Un campo LARGO dentro del par se lleva el renglón entero (chore 90.112: el nombre
   completo y la organización, que la maqueta dibuja a todo lo ancho). Mismo convenio
   que `.bf-fgrid > .full`, para no volver a resolverlo con un `style=` por pantalla.
   En compacto no hace nada: allí ya hay una sola columna. */
.bf-grid2 > .full {
  grid-column: 1 / -1;
}

/* La rejilla PUESTA SOBRE el pozo de una sección (`class="in bf-grid2"`, que es como la
   escriben las fichas). Sin esto no hace nada y no lo dice: `.bf-sect > .in` es
   `display: flex` y le gana por especificidad, así que el par de columnas se quedaba en
   una sola —la ficha de instancia de 90.076 llevaba así desde el día que se escribió, y
   se descubrió montando la de usuario (chore 90.112)—. */
.bf-sect > .in.bf-grid2 {
  display: grid;
}

/* Interruptor con su explicación al lado (chore 90.076): el estado de un registro
   no es un campo más —la baja corta el acceso de todos— y necesita decir qué hace
   junto a lo que se pulsa, no en una ayuda perdida debajo. */
.bf-switch-row {
  display: flex;
  align-items: flex-start;
  gap: 0.875rem;
}

.bf-switch-row > input.bf-switch {
  margin-top: 0.125rem;
}

/* El estado de la sección acompaña al rótulo en el carril, no a los campos: es
   lo que la maqueta pone bajo «Correo (SMTP)» («probado hace 2 h»). */
.bf-frail .bf-pill {
  margin-top: 0.5625rem;
}

/* El dato que gestiona OTRO (chore 90.070 · U-12): mismo pozo, pero sin fondo y
   con el filete discontinuo de la maqueta — se ve, no se escribe. Va sobre un
   div, no sobre un input deshabilitado: no hay nada que enfocar ni que enviar. */
.bf-input.ro {
  background: transparent;
  border-style: dashed;
  color: var(--bf-muted);
}

/* Dentro de la tarjeta de sección solo hay campos, apilados con el aire de la
   maqueta. */
.bf-sect > .in {
  display: flex;
  flex-direction: column;
  gap: 0.9375rem;
}

/* Renglón de interruptor: el que manda a la izquierda con su explicación y el
   secundario al final. Envuelve en compacto en vez de estrujarse. */
.bf-srow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.875rem;
}

/* El interruptor secundario se va al final del renglón… cuando hay renglón: al
   envolver en compacto, alinearlo a la derecha lo dejaría suelto en el aire. */
.bf-srow > .aside {
  display: flex;
  align-items: center;
  gap: 0.625rem;
}

@media (min-width: 620px) {
  .bf-srow > .aside {
    margin-left: auto;
  }
}

/* Renglón de preferencia SIN interruptor a la izquierda (chore 90.070): el
   rótulo con su consecuencia manda, y las opciones se van al final del renglón
   como el .aside del interruptor. */
.bf-srow-main {
  flex: 1 1 auto;
  min-width: 0;
}

.bf-srow-main b {
  font-size: var(--bf-text-body);
  display: block;
}

.bf-srow-main .bf-hint {
  display: block;
  margin-top: 0.125rem;
}

/* El interruptor va dentro de una etiqueta (.bf-check, que ya es «un control y
   su texto en un renglón»), y su explicación cae bajo el rótulo. */
.bf-srow .bf-check b {
  font-size: var(--bf-text-body);
}

.bf-srow .bf-check .bf-hint {
  display: block;
}

/* Rejilla de campos de una sección (chore 90.080 · U-21). Mobile-first (MF-2):
   la base es UNA columna —a 360 px dos campos de correo no caben— y los cortes
   la ensanchan hasta la retícula que dibuja la maqueta.
     .pair    dos columnas desde 620
     .server  servidor + puerto + cifrado; el servidor ocupa el renglón entero
              hasta que a 900 caben los tres al lado (2fr 1fr 1fr, la maqueta)
     .trio    la MISMA retícula para tres campos cuyo primero es el largo
              (chore 90.074: scopes + claim de roles + claim de grupos) */
.bf-fgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 13px;
}

@media (min-width: 620px) {
  .bf-fgrid.pair,
  .bf-fgrid.server,
  .bf-fgrid.trio {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .bf-fgrid.server > :first-child,
  .bf-fgrid.trio > :first-child {
    grid-column: 1 / -1;
  }
}

@media (min-width: 900px) {
  .bf-fgrid.server,
  .bf-fgrid.trio {
    grid-template-columns: 2fr 1fr 1fr;
  }

  .bf-fgrid.server > :first-child,
  .bf-fgrid.trio > :first-child {
    grid-column: auto;
  }
}

/* Un campo LARGO dentro de una rejilla de campos cortos se lleva el renglón entero
   (chore 90.067; la nota de la maqueta dice «dos columnas para lo corto, una para lo
   largo»). En compacto no hace nada: allí ya hay una sola columna. */
.bf-fgrid > .full {
  grid-column: 1 / -1;
}

/* Filete que separa dos secciones dentro de una misma caja: no es un borde de
   superficie —no rodea nada—, es el corte entre lo que se configura y quién lo va a
   administrar. La maqueta lo dibuja con un <div> de 1 px; aquí es el <hr> que ya
   significa eso en HTML, sin el relieve que le pone el navegador. */
.bf-rule {
  height: 1px;
  border: 0;
  margin: 4px 0;
  background: var(--bf-border);
}

/* Lo que se ACTÚA va dentro de la tarjeta, donde están los campos que prueba
   (maqueta): el botón de correo de prueba y lo que dice a dónde va. */
.bf-actrow {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
}

/* Un campo con SU acción al lado (chore 90.074: «Quitar el secreto» junto al campo del
   secreto, como lo dibuja `a-idp-edit`). No es el `.bf-actrow` de arriba —ahí la acción
   prueba lo que hay en la tarjeta entera— sino una acción que sólo tiene sentido sobre
   ESE campo. Mobile-first: el botón baja debajo en cuanto el campo no cabe. */
.bf-fieldact {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 14px;
}

.bf-fieldact > .bf-field {
  flex: 1 1 220px;
  min-width: 0;
}

/* Renglón de una regla de mapeo de rol (chore 90.074 · `a-idp-maps`): grupo del token →
   instancia → rol del producto, con su acción al final. No es una tabla —son tres
   piezas y una flecha, no columnas comparables— y por eso vive dentro de una tarjeta
   con su pie. Mobile-first: en compacto las piezas envuelven y la fila crece hacia
   abajo, que es lo que evita el desplazamiento horizontal (MF-5). */
.bf-maprow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 9px 16px;
  border-bottom: 1px solid var(--bf-border-strong);
}

.bf-maprow > .to {
  color: var(--bf-dim);
}

.bf-maprow > .to > svg {
  display: block;
  width: 0.875rem;
  height: 0.875rem;
}

.bf-maprow > .what {
  flex: 1 1 150px;
  min-width: 0;
  font-size: var(--bf-table-row-font-size);
  font-weight: 600;
}

/* La acción de la regla se va al extremo… cuando hay renglón: al envolver, alinearla a
   la derecha la dejaría suelta en el aire (mismo criterio que `.bf-srow > .aside`). */
@media (min-width: 620px) {
  .bf-maprow > form {
    margin-left: auto;
  }
}

/* Lo que falla se dice al pie de SU regla, en su propio renglón (F-8). */
.bf-maprow > .bf-hint {
  flex: 1 1 100%;
}

/* La fila de ALTA se hunde (M-6): es lo que se está rellenando, no una regla más. */
.bf-maprow.add {
  background: var(--bf-panel-bg);
}

/* La regla que el servidor ya no admite —su instancia se retiró— se marca con el mismo
   halo que un campo con error, porque es exactamente eso: algo que hay que corregir. */
.bf-maprow.bad {
  background: var(--bf-danger-soft);
}

/* Apariencia: los campos y la vista previa de la marca. Apiladas en compacto;
   desde 900 la previa se va a su carril de 230 px, como la maqueta. */
.bf-brand {
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.bf-brand > .fields {
  display: flex;
  flex-direction: column;
  gap: 13px;
  flex: 1;
  min-width: 0;
}

@media (min-width: 900px) {
  .bf-brand {
    flex-direction: row;
    align-items: flex-start;
  }

  .bf-brand > .preview {
    width: 230px;
    flex: none;
  }
}

/* La marca se ve SOBRE LA PIZARRA, que es donde va a verse de verdad (el
   lateral y el acceso), no sobre el papel de la ficha. */
.bf-brandbox {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-top: 7px;
  padding: 14px;
  border-radius: var(--bf-radius-m);
  background: var(--bf-sidebar-bg);
}

.bf-brandbox b {
  display: block;
  color: var(--bf-sidebar-fg);
  font-size: 14px;
  overflow-wrap: anywhere;
}

.bf-brandbox .sub {
  color: var(--bf-sidebar-muted);
  font-size: 10px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

/* El logotipo de la instalación, si lo hay, sustituye a la marca dibujada. */
.bf-brandbox img {
  width: 30px;
  height: 30px;
  flex: none;
  object-fit: contain;
  border-radius: 9px;
}

/* ═══ 9 · Forense de sesión: escenario, transporte e hitos ═══════════════════
   Las piezas de la pantalla «Actividad · forense de sesión» de la maqueta
   (chore 90.054 · unidad F). El escenario es un iframe con la instancia real en
   modo reproducción; encima se superpone el hito y debajo va el transporte.

   Dos piezas de la maqueta NO están aquí, y no por olvido:
     · `.bf-guest` — la instancia embebida está pintada a mano en la maqueta
       porque allí no hay iframe que cargar. Aquí la pinta la instancia.
     · `.bf-click` — el clic simulado necesita saber DÓNDE se pulsó, y el asiento
       de 10.001 no lo trae. Marcar un punto inventado sería peor que no marcarlo.
   Y una diferencia de medida: en la maqueta el escenario se estira con el alto
   del cuerpo; el área de administración no fija ese alto, así que aquí manda un
   `min-height` y la pieza crece con la página.                                */

/* Mobile-first: por debajo del corte «completo» (1100, MF-3) la lista de hitos
   pasa debajo del escenario en vez de estrujar la pantalla reproducida; desde
   1100 recupera su carril lateral de 296 px, que es como lo dibuja la maqueta. */
.bf-replay {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
}

.bf-stage-box {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.bf-replay-aside {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
}

@media (min-width: 1100px) {
  .bf-replay {
    flex-direction: row;
  }

  .bf-replay-aside {
    width: 296px;
    flex: none;
  }
}

/* En 296 px la clave de la ficha se lleva 116 px y el valor se parte en dos
   líneas; la maqueta usa 84 px justo aquí. Es medida de esta columna, no del
   catálogo: la lista de definiciones general no cambia. */
.bf-replay-aside .bf-dl {
  grid-template-columns: 84px minmax(0, 1fr);
}

/* ── Escenario ─────────────────────────────────────────────────────────────── */

.bf-viewport {
  flex: 1;
  min-height: 420px;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
  overflow: hidden;
  background: var(--bf-panel-bg);
  display: flex;
  flex-direction: column;
  position: relative;
}

/* Mobile-first: la barra imita la de un navegador y a 360 px sus tres piezas no
   caben en un renglón —la pastilla de «solo lectura» se lleva 207 de 285 px y deja
   la URL en 22, que no es leer nada—. Por debajo de 620 los puntos de adorno se
   retiran (son dibujo, no dato) y la pastilla baja a su línea; desde 620 vuelve el
   renglón único de 34 px de la maqueta. La URL es el dato forense de la pantalla
   —en qué página estaba—, así que es lo que conserva el ancho. */
.bf-urlbar {
  min-height: 34px;
  flex: none;
  background: var(--bf-panel-bg);
  border-bottom: 1px solid var(--bf-border);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  padding: 5px 12px;
}

.bf-urlbar .dots {
  display: none;
  gap: 5px;
}

.bf-urlbar .dots i {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--bf-border);
}

.bf-urlbar .url {
  flex: 1;
  min-width: 0;
  height: 22px;
  border-radius: 999px;
  background: var(--bf-surface-bg);
  border: 1px solid var(--bf-border);
  display: flex;
  align-items: center;
  padding: 0 10px;
  font-family: var(--bf-font-mono);
  font-size: 11px;
  color: var(--bf-muted);
  gap: 7px;
  white-space: nowrap;
  overflow: hidden;
}

.bf-urlbar .url b {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

/* La pastilla baja a su propio renglón por debajo de 620: mide 207 px fijos y no
   encoge, así que compartiendo línea dejaba la URL en 46 px —cinco caracteres— sin
   llegar nunca a envolver, porque la URL sí encoge y las dos «cabían». Ocupando la
   línea entera, la de arriba queda para la dirección. Desde 620 vuelve al renglón
   único de la maqueta. */
.bf-replay-tag {
  flex: 0 0 100%;
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bf-accent-text);
  background: var(--bf-accent-soft);
  border: 1px solid color-mix(in srgb, var(--bf-accent) 35%, transparent);
  border-radius: 999px;
  padding: 2px 9px;
  white-space: nowrap;
}

/* Desde 620 la barra vuelve a ser el renglón único de 34 px de la maqueta: los tres
   trozos en línea y la pastilla a su ancho. Va aquí abajo, y no junto a `.bf-urlbar`,
   porque tiene que ganar a la declaración base de `.bf-replay-tag`: una consulta de
   medios no añade especificidad, así que el orden en el fichero es lo que decide. */
@media (min-width: 620px) {
  .bf-urlbar {
    height: 34px;
    flex-wrap: nowrap;
    gap: 10px;
    padding: 0 12px;
  }

  .bf-urlbar .dots {
    display: flex;
  }

  .bf-replay-tag {
    flex: none;
  }
}

.bf-frame {
  flex: 1;
  position: relative;
  overflow: hidden;
  background: var(--bf-app-bg);
}

.bf-frame iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* Lo que se ve cuando no hay escenario que cargar (rastro del núcleo, instancia
   sin URL registrada) o mientras el hito no tiene página conocida. */
.bf-frame .bf-stage-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 24px;
  color: var(--bf-muted);
  font-size: 12.5px;
}

/* ── Escenario de datos del hito (rediseño del 2026-10-15) ───────────────────────
   El escenario dejó de ser la pantalla en vivo (iframe). Reparte en dos, la
   CAPTURA reducida del hito y lo que el producto envió, y quién manda depende del
   hito: en una NAVEGACIÓN (.is-nav) la captura va grande y los datos en una
   columna estrecha; en una ACCIÓN (.is-act) los datos van grandes —qué cambió es
   lo que se viene a ver— y la captura se queda pequeña, de contexto. La altura la
   ponen ellos, no el alto de la página: el `min-height` de 420 del escenario en
   vivo aquí no aplica.
   Mobile-first: por debajo de 900 todo va en una columna. */
.bf-act-viewport {
  flex: none;
  min-height: 0;
}

.bf-act-stage {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  padding: 14px;
  background: var(--bf-surface-bg);
}

@media (min-width: 900px) {
  .bf-act-stage {
    align-items: start;
  }

  .bf-act-stage.is-nav {
    grid-template-columns: minmax(0, 1fr) 280px;
  }

  .bf-act-stage.is-act {
    grid-template-columns: 300px minmax(0, 1fr);
  }
}

/* Los datos no estiran el escenario: si una acción trae muchos campos, se
   desplazan dentro. */
.bf-act-side {
  min-width: 0;
  max-height: 60vh;
  overflow: auto;
}

/* En una acción los datos son lo protagonista: vuelven a su letra de lectura. */
.bf-act-stage.is-act .bf-act-head h2 {
  font-size: 15px;
}

.bf-act-stage.is-act .bf-act-head h2 .bf-tagk {
  font-size: 13px;
}

.bf-act-stage.is-act .bf-act-sub {
  font-size: 12.5px;
}

.bf-act-panel {
  display: flex;
  flex-direction: column;
  gap: 8px;
  min-width: 0;
}

.bf-act-panel[hidden] {
  display: none;
}

.bf-act-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 12px;
}

.bf-act-head h2 {
  margin: 0;
  font-size: 13.5px;
  font-weight: 600;
  color: var(--bf-surface-fg);
  min-width: 0;
  overflow-wrap: anywhere;
}

.bf-act-head h2 .bf-tagk {
  font-size: 12px;
  color: var(--bf-surface-fg);
}

.bf-act-head time {
  margin-left: auto;
  font-family: var(--bf-font-mono);
  font-size: 11px;
  color: var(--bf-muted);
}

/* Qué tipo de hito es, con la misma marca que la línea de pasos: navegación en el
   tono neutro, acción en el naranja de la marca. */
.bf-act-kind {
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-act-kind.act {
  color: var(--bf-accent-text);
}

.bf-act-sub,
.bf-act-none {
  margin: 0;
  font-size: 12px;
  color: var(--bf-muted);
}

.bf-act-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bf-act-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  font-size: 12px;
}

.bf-act-list time,
.bf-act-list .count {
  font-family: var(--bf-font-mono);
  font-size: 11.5px;
  color: var(--bf-muted);
}

/* El contexto del evento va debajo de la captura y los datos, a todo el ancho del
   visor. */
.bf-act-context {
  grid-column: 1 / -1;
  min-width: 0;
}

.bf-act-context[hidden] {
  display: none;
}

.bf-act-context .bf-json {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.bf-act-context summary {
  cursor: pointer;
  font-size: 12px;
  color: var(--bf-muted);
  margin-bottom: 6px;
}

/* La captura reducida del hito (fase B3): la imagen que pintó Bifrost o, sin ella,
   el hueco, que guarda la proporción de una pantalla para que el escenario no
   cambie de forma de un hito a otro. Pulsarla la amplía. */
.bf-shot {
  margin: 0;
}

/* El marco mide SIEMPRE lo mismo —el ancho del escenario en 16:10, la proporción
   de una pantalla de escritorio— y la captura se encaja entera dentro. Antes salía
   a su tamaño real, que es el de la ventana de quien navegaba: cada hito medía
   distinto y el escenario cambiaba de forma al avanzar. El marco es invisible: el
   borde lo lleva la imagen, así lo que la captura no llena queda en blanco y no
   como una banda gris pegada a ella. */
.bf-shot-open {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  aspect-ratio: 16 / 10;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
}

.bf-shot-open[hidden],
.bf-shot-empty[hidden] {
  display: none;
}

/* Entera y sin deformar, a su proporción: una ventana estrecha o alta deja hueco a
   los lados o debajo, y la parte de arriba —cabecera y título de la página— queda
   siempre a la vista. */
.bf-shot-img {
  display: block;
  max-width: 100%;
  max-height: 100%;
  min-width: 0;
  min-height: 0;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-m);
}

/* La captura ampliada: un diálogo modal con la imagen a su tamaño, sin pasar de la
   ventana. Se cierra pulsándolo o con Esc. */
.bf-shot-zoom {
  max-width: min(96vw, 1440px);
  max-height: 92vh;
  padding: 0;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-m);
  background: var(--bf-panel-bg);
  cursor: zoom-out;
}

.bf-shot-zoom::backdrop {
  background: rgb(0 0 0 / 0.6);
}

.bf-shot-zoom img {
  display: block;
  max-width: 100%;
  max-height: 92vh;
  object-fit: contain;
}

.bf-shot-empty {
  aspect-ratio: 16 / 10;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 12px;
  text-align: center;
  font-size: 12px;
  color: var(--bf-dim);
  background: var(--bf-panel-bg);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-m);
}

/* ── Aviso del hito ────────────────────────────────────────────────────────────
   La acción no cambia de página: se superpone sobre la de su navegación padre.
   Desde la chore 90.258 es el aviso emergente del chasis (§5) —cristal, medalla
   y entrada— con tres diferencias, porque NO es pasajero: vive mientras su hito
   está elegido.
     · El SITIO es abajo a la derecha de SU ESCENARIO y no de la ventana: sigue
       `absolute` dentro de .bf-frame, encima de la pantalla que reproduce.
     · No lleva barra ni se va solo: no le queda tiempo, le queda el hito.
     · La medalla no dice un tono —una acción reproducida no está «bien» ni
       «mal»—: lleva la marca de las acciones de la línea de pasos, el bolo
       cuadrado en naranja, para que se lea como el mismo hito dicho dos veces.
   Iba SIEMPRE sobre pizarra con la clave en --bf-accent, el naranja de relleno.
   Sobre cristal la clave lleva --bf-glass-accent-ink: detrás tiene la pantalla de
   OTRO producto, clara u oscura con independencia del tema, y el naranja legible
   a secas se caía con la de signo contrario (las medidas, en tokens.css). */

.bf-frame .bf-toast {
  position: absolute;
  z-index: 1;
  right: 1rem;
  bottom: 1rem;
  min-width: 0;
  max-width: calc(100% - 2rem);
}

.bf-frame .bf-toast-mark {
  background-color: color-mix(in srgb, var(--bf-accent) 18%, transparent);
  color: var(--bf-accent);
}

.bf-frame .bf-toast-mark::after {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 2px;
  -webkit-mask: none;
  mask: none;
}

/* La línea del hito: clave, dónde y cuándo, separados por filetes. */
.bf-toast-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 11px;
  min-width: 0;
  padding-top: 0.1875rem;
}

/* La clave de la acción es un dato de fuera y llega a medir 290 px
   (`bifrost.instance.product.permission.granted`): con `nowrap` se salía del aviso y
   el escenario la recortaba justo por el final, que es la parte que distingue una
   acción de otra. En compacto ocupa su propio renglón del aviso y se parte si aún no
   cabe; desde 620 vuelve al renglón único de la maqueta.

   El reparto es `0 0 100%` con `min-width: 0`, y no un simple `overflow-wrap`: el
   aviso se ajusta a su contenido, así que sin fijarle la base la clave se llevaba por
   delante el ancho del aviso (con `nowrap`) o lo encogía a una columna de letras (con
   `anywhere`). Con la base al 100 % la medida la pone el aviso y la clave se acomoda
   dentro. */
.bf-toast .k {
  flex: 0 0 100%;
  min-width: 0;
  font-family: var(--bf-font-mono);
  font-size: 11.5px;
  color: var(--bf-glass-accent-ink);
  overflow-wrap: break-word;
}

@media (min-width: 620px) {
  .bf-toast-line {
    flex-wrap: nowrap;
    gap: 11px;
  }

  .bf-toast .k {
    flex: none;
    white-space: nowrap;
  }
}

.bf-toast .sep {
  width: 1px;
  height: 16px;
  flex: none;
  background: var(--bf-border);
}

/* La hora, en un gris MÁS CERCANO a la tinta que el apagado de la casa: el apagado
   se queda en 3,9:1 sobre el cristal oscuro con una página blanca detrás (y en 4,3 en
   claro con una negra). A medio camino entre los dos sigue siendo lo secundario del
   rótulo y pasa de 4,5:1 con cualquier pantalla detrás. */
.bf-toast time {
  color: color-mix(in srgb, var(--bf-muted), var(--bf-surface-fg) 50%);
  white-space: nowrap;
}

/* ── Línea de pasos ────────────────────────────────────────────────────────── */

/* Mobile-first: la línea reparte el ancho entre los pasos, y eso solo funciona
   mientras haya ancho que repartir. Medido a 360 px con una sesión de 20 pasos:
   cada uno salía a CERO píxeles, los veinte bolos se apilaban en el mismo punto y
   diecinueve quedaban fuera del `overflow: hidden` —invisibles y no pulsables—. Por
   debajo del corte «completo» (1100, el mismo con el que la pantalla apila) el paso
   deja de encogerse y la línea se desplaza dentro de su tarjeta; desde 1100 vuelve
   el reparto de la maqueta, que ahí sí tiene sitio.

   Los 78 px del paso no son un número redondo: es lo que ocupa un rótulo de hora
   con el cuerpo de 10,5 px sin recortarse, que es lo mínimo para saber a qué paso
   se salta. El módulo del cliente lleva el paso vigente a la vista al avanzar. */
.bf-steps {
  display: flex;
  align-items: flex-start;
  gap: 0;
  /* Los pasos se miden respecto a la línea (ts/audit/replay.ts, `stepLine`). */
  position: relative;
  padding: 14px 16px 6px;
  overflow-x: auto;
}

.bf-stepx {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 7px;
  position: relative;
  flex: 1 0 78px;
  min-width: 0;
  background: none;
  border: 0;
  padding: 0;
  font-family: inherit;
  cursor: pointer;
}

@media (min-width: 1100px) {
  .bf-steps {
    overflow: hidden;
  }

  .bf-stepx {
    flex: 1;
  }
}

.bf-stepx .line {
  position: absolute;
  top: 7px;
  left: calc(50% - var(--reach, 100%));
  right: 50%;
  height: 2px;
  background: var(--bf-border);
}

.bf-stepx:first-child .line {
  display: none;
}

/* La línea enseña una VENTANA de hitos alrededor del vigente (2026-10-19, ts/audit/replay.ts):
   los de fuera no se pintan y cada extremo cuenta los que quedan («+38»). El primer paso de
   la ventana sólo lleva el tramo de línea de su izquierda si hay hitos antes: es el que la
   une al marcador. */
.bf-stepx.is-out {
  display: none;
}

.bf-steps-more[hidden] + .bf-stepx .line {
  display: none;
}

.bf-steps-more {
  flex: none;
  align-self: flex-start;
  height: 17px;
  margin-top: -1px;
  padding: 0 7px;
  border: 1px dashed var(--bf-border);
  border-radius: 999px;
  background: var(--bf-surface-bg);
  color: var(--bf-muted);
  font-family: var(--bf-font-mono);
  font-size: 10.5px;
  line-height: 15px;
  cursor: pointer;
  position: relative;
  z-index: 1;
}

.bf-steps-more:hover {
  border-color: var(--bf-accent);
  color: var(--bf-accent);
}

.bf-steps-more[hidden] {
  display: none;
}

.bf-stepx.done .line {
  background: var(--bf-accent);
}

.bf-stepx .knob {
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--bf-surface-bg);
  border: 2px solid var(--bf-border);
  position: relative;
  z-index: 1;
}

.bf-stepx.done .knob {
  border-color: var(--bf-accent);
  background: var(--bf-accent);
}

.bf-stepx.now .knob {
  border-color: var(--bf-accent);
  background: var(--bf-surface-bg);
  box-shadow: 0 0 0 4px var(--bf-accent-soft);
}

/* El paso cuadrado es una ACCIÓN: no cambia de página, se superpone. */
.bf-stepx.act .knob {
  border-radius: 3px;
}

.bf-stepx span {
  font-size: 10.5px;
  color: var(--bf-dim);
  text-align: center;
  line-height: 1.3;
  max-width: 96%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-stepx.now span {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

/* El paso VIGENTE se lleva el sitio que necesita su rótulo (2026-10-19). Con la ventana de
   25 hitos (ts/audit/replay.ts) los demás rótulos se ven todos, cortados a su paso; el del
   vigente se lee entero porque su paso mide lo que su rótulo, con un tope. La línea de cada
   paso nace en el centro del anterior (`--reach`, que mide el módulo), así que los tramos
   siguen unidos aunque los pasos ya no midan lo mismo.

   Reparte como los demás y solo se ensancha si su rótulo no cabe en su parte (`min-width`), no
   `flex: 0 0 auto`: con pocos hitos el vigente se quedaba en lo que mide su rótulo y los otros se
   repartían el resto, así que los bolos saltaban de sitio al avanzar (3 hitos: el vigente pegado a
   un extremo y los otros dos amontonados). */
.bf-stepx.now {
  min-width: max-content;
  padding: 0 14px;
}

.bf-stepx.now span {
  max-width: 280px;
  font-size: 12px;
  color: var(--bf-accent);
}

/* ── Transporte ────────────────────────────────────────────────────────────── */

.bf-transport {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 10px 16px 14px;
  border-top: 1px solid var(--bf-border);
  flex-wrap: wrap;
}

/* I-4: los mandos del transporte miden --bf-control-height, como el resto (eran de 30 y 38 px). */
.bf-tbtn {
  width: var(--bf-control-height);
  height: var(--bf-control-height);
  flex: none;
  border-radius: var(--bf-radius-m);
  border: 1px solid var(--bf-field-line);
  background: var(--bf-surface-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--bf-muted);
  font-size: 12px;
  font-family: inherit;
  cursor: pointer;
}

.bf-tbtn:disabled {
  opacity: 0.45;
  cursor: default;
}

.bf-tbtn.main {
  border-radius: 50%;
  background: var(--bf-accent);
  border-color: var(--bf-accent);
  color: var(--bf-accent-fg);
}

/* Saltar de hito (|◀ ▶|): del alto del triángulo de reproducir. */
.bf-skip {
  width: 14px;
  height: 14px;
  flex: none;
}

.bf-pause {
  display: flex;
  gap: 3px;
}

.bf-pause i {
  width: 3.5px;
  height: 13px;
  background: currentColor;
  border-radius: 1px;
}

/* Triángulo de reproducir: el mismo de la maqueta (.bf-play .tri). */
.bf-tri {
  width: 0;
  height: 0;
  border-left: 10px solid currentColor;
  border-top: 6px solid transparent;
  border-bottom: 6px solid transparent;
  margin-left: 3px;
}

.bf-clock {
  font-family: var(--bf-font-mono);
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.bf-clock small {
  color: var(--bf-dim);
  font-size: 11.5px;
}

/* Progreso del transporte del forense. Se llamaba `.bf-bar` y CHOCABA con la barra
   comparable de la tarjeta de producto (§6), que lleva ese nombre porque es el que le da
   la maqueta: al ser esta regla la de más abajo, ganaba, y el relleno de la tarjeta se
   pintaba absoluto sobre toda la fila, tapando su rótulo. Renombrada en la chore 90.075
   —el nombre de la maqueta se queda con la pieza del catálogo— y acotada a su barra de
   transporte, que es de donde nunca debió salir. */
.bf-transport .bf-progress {
  flex: 1;
  min-width: 90px;
  height: 5px;
  border-radius: 3px;
  background: var(--bf-border);
  position: relative;
}

.bf-transport .bf-progress i {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  background: var(--bf-accent);
  border-radius: 3px;
}

/* ── Lista de hitos ────────────────────────────────────────────────────────── */

/* Con un tope de alto: sin él, `overflow: auto` no tenía nada que recortar y una
   sesión larga estiraba la columna (y la página) hacia abajo sin fin. Rueda la
   lista y no la página; replay.ts trae el hito vigente a la vista moviendo solo
   la lista. El tope deja ver unos seis hitos: lo justo para el contexto sin que
   la tarjeta caiga por debajo del pliegue. */
.bf-hits {
  display: flex;
  flex-direction: column;
  max-height: min(34vh, 300px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ── El reproductor a la medida de la ventana (petición del usuario, 2026-10-19) ──
   «La barra de reproducción está cambiando continuamente de sitio»: el escenario
   tomaba el alto de lo que enseñaba cada hito —una captura, una acción de un campo,
   otra de doce— y el transporte bajaba y subía con él. En escritorio el reproductor
   ocupa ahora justo lo que queda de ventana (`--bf-replay-h`, que mide replay.ts):
   el transporte va fijo abajo, el escenario se queda con el resto y no cambia de
   tamaño al avanzar, y lo que no cabe rueda dentro de su columna. El reparto sigue
   al hito: en una navegación manda la captura y en una acción, lo que cambió.
   Por debajo de 1100 de ancho o 640 de alto no hay sitio para tenerlo todo a la
   vista y se queda como estaba, fluyendo con la página. */
@media (min-width: 1100px) and (min-height: 640px) {
  .bf-replay {
    height: var(--bf-replay-h, auto);
    min-height: 520px;
  }

  .bf-replay .bf-stage-box {
    min-height: 0;
  }

  .bf-replay .bf-act-viewport {
    flex: 1;
    min-height: 0;
  }

  .bf-replay .bf-stage-box > .bf-card {
    flex: none;
  }

  .bf-replay .bf-act-stage {
    flex: 1;
    min-height: 0;
    grid-template-rows: minmax(0, 1fr);
    align-items: stretch;
  }

  .bf-replay .bf-act-stage.is-nav {
    grid-template-columns: minmax(0, 1fr) minmax(260px, 26%);
  }

  .bf-replay .bf-act-stage.is-act {
    grid-template-columns: minmax(320px, 42%) minmax(0, 1fr);
  }

  /* La captura llena su columna a lo alto y se encaja entera, arriba y centrada. */
  .bf-replay .bf-shot {
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .bf-replay .bf-shot-open,
  .bf-replay .bf-shot-empty {
    flex: 1;
    min-height: 0;
    aspect-ratio: auto;
  }

  /* En una navegación la captura llena su columna a TODO EL ALTO, arriba a la
     izquierda y sin deformarse. La columna mide lo que la captura a ese alto
     (`--bf-shot-w`, que pone replay.ts) sin quitar a los datos sus 260 de mínimo; si
     la captura es aún más apaisada, el MARCO cede: el escenario baja hasta el alto
     de la captura a ese ancho (`--bf-stage-h` y `.is-fit`, de replay.ts), y la
     imagen lo llena entero a lo ancho y a lo alto, sin recorte ni banda. Lo que
     sobra de ventana queda bajo el marco, fuera de él. Pulsarla la sigue ampliando. */
  .bf-replay .bf-act-stage.is-nav {
    grid-template-columns: var(--bf-shot-w, minmax(0, 1fr)) minmax(260px, 1fr);
  }

  .bf-replay .bf-act-stage.is-nav .bf-shot-open {
    justify-content: flex-start;
    overflow: hidden;
  }

  .bf-replay .bf-act-stage.is-nav .bf-shot-img {
    flex: none;
    width: 100%;
    height: auto;
    max-width: none;
    max-height: none;
  }

  .bf-replay .bf-act-viewport.is-fit {
    flex: none;
  }

  .bf-replay .bf-act-viewport.is-fit > .bf-act-stage {
    flex: none;
    height: var(--bf-stage-h);
  }

  /* El transporte no sube con el marco: sigue fijo abajo. */
  .bf-replay .bf-stage-box:has(> .bf-act-viewport.is-fit) > .bf-card {
    margin-top: auto;
  }

  /* En una acción la captura va arriba a su tamaño: el marco sigue a la imagen y no
     a un 16:10 fijo, que dejaba una banda vacía entre la captura y el contexto. Sin
     captura, el hueco guarda el 16:10. Si el hito trae contexto, el escenario pasa a
     dos filas: arriba la captura y los datos, y abajo el contexto a todo el ancho
     con lo que quede. La fila de arriba mide lo que la captura a todo el ancho de su
     columna (petición del usuario, 2026-10-19: ni recortada ni con banda): el marco
     sigue a la imagen. Los datos no estiran esa fila (`contain: size`): ruedan
     dentro de ella. Pulsar la captura la sigue ampliando. */
  .bf-replay .bf-act-stage.is-act:has(> .bf-act-context:not([hidden])) {
    grid-template-rows: auto minmax(0, 1fr);
  }

  .bf-replay .bf-act-stage.is-act:has(> .bf-act-context:not([hidden])) .bf-act-side {
    contain: size;
  }

  .bf-replay .bf-act-stage.is-act .bf-shot-open {
    flex: none;
    aspect-ratio: auto;
  }

  .bf-replay .bf-act-stage.is-act .bf-shot-img {
    width: 100%;
    height: auto;
  }

  .bf-replay .bf-act-stage.is-act .bf-shot-empty {
    flex: none;
    aspect-ratio: 16 / 10;
  }

  .bf-replay .bf-act-context[open]:not([hidden]) {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  /* Lo que va tras el `summary` vive en su propia caja (`::details-content`):
     también se estira, o el JSON se quedaría a su alto dentro de un `details`
     que sí llega al pie. */
  .bf-replay .bf-act-context[open]:not([hidden])::details-content {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .bf-replay .bf-act-context[open] .bf-json {
    flex: 1;
    min-height: 0;
    max-height: none;
    overflow: auto;
  }

  .bf-replay .bf-act-side {
    max-height: none;
    min-height: 0;
  }

  /* La lista de hitos se queda con el alto que deja la ficha de la sesión. */
  .bf-replay .bf-replay-aside {
    min-height: 0;
  }

  .bf-replay .bf-replay-aside > .bf-card:last-child {
    flex: 1;
    min-height: 0;
    display: flex;
    flex-direction: column;
  }

  .bf-replay .bf-hits {
    flex: 1;
    min-height: 0;
    max-height: none;
  }
}

.bf-hit {
  display: flex;
  gap: 10px;
  padding: 6px 12px;
  border-bottom: 1px solid var(--bf-border-soft);
  font-size: 12.5px;
  align-items: flex-start;
  text-align: left;
  width: 100%;
  background: none;
  border-left: 0;
  border-right: 0;
  border-top: 0;
  font-family: inherit;
  color: inherit;
  cursor: pointer;
}

.bf-hit:last-child {
  border-bottom: 0;
}

.bf-hit .t {
  font-family: var(--bf-font-mono);
  font-size: 11px;
  color: var(--bf-dim);
  padding-top: 2px;
  flex: none;
}

.bf-hit .txt {
  min-width: 0;
}

.bf-hit .txt b {
  font-weight: 600;
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
}

.bf-hit .txt span {
  font-family: var(--bf-font-mono);
  font-size: 10.5px;
  color: var(--bf-dim);
  overflow-wrap: anywhere;
}

/* Sangrado = cuelga de su navegación: es una acción dentro de esa página. */
.bf-hit.child {
  padding-left: 30px;
  background: color-mix(in srgb, var(--bf-panel-bg) 60%, transparent);
}

.bf-hit.child .txt b {
  color: var(--bf-accent-text);
  font-family: var(--bf-font-mono);
  font-size: 11.5px;
}

.bf-hit.now {
  background: var(--bf-accent-soft);
  box-shadow: inset 2px 0 0 var(--bf-accent);
}

/* ═══ 10 · Ficha del asiento y exportación (chore 90.053) ════════════════════
   La maqueta pinta el cajón y el modal sobre un LIENZO: por eso .bf-scrim,
   .bf-drawer y .bf-modal-wrap son `absolute`, que ahí es lo correcto. En una
   página de verdad no hay lienzo, y el ancestro posicionado más cercano sería
   cualquiera; lo único que aquí se añade es la capa que los ancla a la VENTANA.
   Las piezas de dentro no se tocan: siguen siendo las del catálogo. */

.bf-overlay {
  position: fixed;
  inset: 0;
  z-index: 50;
}

/* La ficha de un asiento con muchos campos no cabe: el cajón desplaza su cuerpo
   (la pieza del catálogo lo deja en `hidden`, que ahí basta porque la maqueta
   enseña una ficha corta). Cabecera y pie se quedan quietos. */
.bf-overlay .bf-drawer .in {
  overflow-y: auto;
}

/* …pero desplazar el cuerpo solo sirve si el cuerpo llega a sobrar, y en una
   columna flexible los hijos ENCOGEN antes que desbordar. La comparación y el JSON
   tienen `overflow` propio, así que su tamaño mínimo automático es cero y se dejan
   estrujar hasta donde haga falta: medido a 360 px, una comparación de 12 campos
   pasaba de 915 px de alto a 333 y recortaba ocho filas SIN scroll por ninguna
   parte —ni dentro de la pieza ni en el cuerpo, que así nunca sobra—. Fijando el
   tamaño de los hijos, lo que sobra vuelve a ser el cuerpo y el cajón lo desplaza,
   que es lo que la regla de arriba pretendía. */
.bf-overlay .bf-drawer .in > * {
  flex: none;
}

/* Mobile-first: en compacto el cajón ocupa la pantalla —a 540 px fijos dejaría
   una rendija inútil de listado a la izquierda—; desde 620 (MF-3) vuelve a ser
   el lateral de 540 px del catálogo. */
.bf-overlay .bf-drawer {
  width: 100%;
  border-left: 0;
}

@media (min-width: 620px) {
  .bf-overlay .bf-drawer {
    width: 540px;
    border-left: 1px solid var(--bf-border);
  }
}

/* El diálogo de exportación va CENTRADO y sin scroll a su alrededor: si crece más
   que la ventana, se sale por los dos extremos a la vez. Medido a 360 × 400 (un
   teléfono apaisado): 454 px de alto, el título fuera por arriba y «Descargar CSV»
   fuera por abajo, sin manera de alcanzarlos. Se le pone techo y el texto —que es
   lo que crece, porque el aviso de la IP completa depende del filtro— se desplaza
   dentro; cabecera y pie se quedan siempre a la vista, que es donde está el
   consentimiento y donde está la descarga. */
.bf-overlay .bf-modal {
  display: flex;
  flex-direction: column;
  max-height: calc(100vh - 24px);
  max-height: calc(100dvh - 24px);
}

.bf-overlay .bf-modal .in {
  overflow-y: auto;
}

/* Los valores de la comparación son datos de fuera y pueden ser largos: en el
   cajón se parten en varias líneas en vez de cortarse con puntos suspensivos,
   que en una auditoría escondería justo el final del valor que cambió. */
.bf-diff.wrap .row > i {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  text-overflow: clip;
}

/* «Solo lo que cambió»: en un objeto de treinta campos con dos cambios, lo igual
   es ruido. Lo enciende el módulo del cliente; sin él se ve todo. */
.bf-diff.changes-only .row.same {
  display: none;
}

/* Contexto del evento: JSON tal como llegó, indentado. Con tope de altura porque
   es el pie de la ficha, no su asunto. */
.bf-json {
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-m);
  background: var(--bf-panel-bg);
  color: var(--bf-muted);
  font-family: var(--bf-font-mono);
  font-size: var(--bf-legend-font-size);
  line-height: 1.5;
  max-height: 220px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* ═══ 11 · Panorama de actividad (chore 90.059) ══════════════════════════════
   Las piezas de la pantalla «Actividad · panorama» de la maqueta Bifrost-3, la
   pestaña que precede a los asientos. .bf-hours, .bf-heat y .bf-axis se llaman
   como allí y se pintan igual; el reparto (.bf-overview), los avisos
   (.bf-alerts) y el ranking (.bf-rank) son piezas nuevas, porque la maqueta los
   resuelve con estilo suelto dentro de la propia pantalla y una vista no puede.

   El color aquí es SEMÁNTICO y no decorativo (M-2/M-3): el rojo es lo rechazado
   y lo que reventó, el naranja es «aquí estás» —el tramo en curso— y la escala
   del mapa de calor, que es intensidad y no estado. Nada lleva su propia paleta:
   todo sale de los tokens. */

/* Mobile-first: por debajo de 900 (MF-3) las dos columnas apilan —el carril de
   296 px dejaría el panel de barras en una rendija donde no se distingue un
   tramo de otro—; desde 900 vuelve el reparto de la maqueta. */
.bf-overview {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

.bf-overview > .col {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-width: 0;
}

@media (min-width: 900px) {
  .bf-overview {
    grid-template-columns: minmax(0, 1fr) 296px;
  }
}

/* ── Desplazamiento propio de una pieza y su eje ────────────────────────────
   Envoltorio de la pieza que cuenta el tiempo y del EJE que la rotula, juntos: las
   dos tienen que moverse a la vez, porque un eje que no acompaña a lo que rotula
   miente. Lo usa el mapa de calor (unidad R-5), cuyas doce franjas de dos horas son
   del PERIODO y no del ancho de la pantalla: a 360 px la celda salía a 12 px y el
   rótulo de la acción, recortado a 84. La regla que da suelo a las columnas está en
   la propia pieza; esto solo pone dónde se desplaza. */
.bf-scrollx {
  overflow-x: auto;
}

/* ── Barras por tramo: cuánto y cuánto de ello salió mal ────────────────────
   Los dos segmentos se apilan y el rojo va ARRIBA, que es donde se ve sin
   buscarlo. La altura la pone la vista en % de la barra más alta del periodo.

   Las barras se quedan SIN suelo y sin desplazamiento, al revés que el mapa de
   calor de abajo, y es una decisión medida y no un olvido: a 360 px con la ventana
   de 30 días cada tramo sale a 5,8 px, pero lo que aquí se lee es la FORMA —dónde
   está el pico y dónde hay rojo—, no el valor de un tramo suelto, y el segmento
   rojo ya tiene su propio suelo de 6 px en la vista para no desaparecer. Darles
   suelo obligaría además a repetir la medida en su eje, que rotula CINCO marcas
   sobre treinta barras y no comparte rejilla con ellas: dos anchos calculados
   aparte que se desalinearían al primer cambio. La cifra exacta de cada tramo se
   lee en el `title` de la barra y en los avisos de al lado. */

.bf-hours {
  display: flex;
  align-items: flex-end;
  gap: 3px;
  height: 64px;
}

.bf-hours .h {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  gap: 2px;
  height: 100%;
  min-width: 0;
}

.bf-hours .h i {
  display: block;
  border-radius: 1px;
}

.bf-hours .h .okp {
  background: color-mix(in srgb, var(--bf-success) 60%, transparent);
}

.bf-hours .h .badp {
  background: var(--bf-danger);
}

/* El tramo EN CURSO en naranja (M-3: «aquí estás»), no en otro verde: lo que
   distingue no es el estado del tramo sino que es el de ahora mismo. */
.bf-hours .h.now .okp {
  background: var(--bf-accent);
}

.bf-hours-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
  font-size: 9.5px;
  color: var(--bf-dim);
  font-variant-numeric: tabular-nums;
}

/* ── Mapa de calor: qué se hace, y a qué hora ───────────────────────────────
   Una fila por acción y doce columnas de dos horas. La celda apagada es filete,
   no el nivel más flojo: un cero pintado se confundiría con «poco». */

/* El mapa se desplaza dentro de su tarjeta en vez de estrujarse (unidad R-5): la
   celda tiene SUELO de 22 px y el carril del rótulo se queda en los 132 px de la
   maqueta a cualquier ancho. Antes el carril cedía a 84 px en compacto para que las
   doce celdas conservaran algo de ancho, y el resultado medido a 360 px era lo peor
   de las dos cosas: celdas de 12 px y el nombre de la acción recortado.

   `width: max-content` con `min-width: 100%` es lo que mantiene el mapa y su eje
   pegados: los dos declaran la MISMA rejilla, así que con el ancho del contenido
   los dos resuelven 435 px y con sitio de sobra los dos reparten el sobrante entre
   sus doce columnas. Ninguna medida se calcula dos veces. */
.bf-heat {
  display: grid;
  grid-template-columns: 132px repeat(12, minmax(22px, 1fr));
  gap: 3px;
  align-items: center;
  width: max-content;
  min-width: 100%;
}

.bf-heat + .bf-heat {
  margin-top: 6px;
}

.bf-heat .lbl {
  font-family: var(--bf-font-mono);
  font-size: 11px;
  color: var(--bf-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-heat .c {
  height: 17px;
  border-radius: 3px;
  background: var(--bf-border-soft);
}

.bf-heat .c.l1 {
  background: color-mix(in srgb, var(--bf-accent) 18%, transparent);
}

.bf-heat .c.l2 {
  background: color-mix(in srgb, var(--bf-accent) 38%, transparent);
}

.bf-heat .c.l3 {
  background: color-mix(in srgb, var(--bf-accent) 62%, transparent);
}

.bf-heat .c.l4 {
  background: var(--bf-accent);
}

/* La fila de una acción de rechazo va entera en rojo: es la que se busca. */
.bf-heat .c.bad {
  background: var(--bf-danger);
}

/* El mapa de calor y su eje comparten rejilla: se ensanchan juntos y se desplazan
   juntos dentro de `.bf-scrollx`. Si esta declaración deja de ser la misma que la
   de `.bf-heat`, el eje deja de rotular la columna que tiene encima. */
.bf-axis {
  display: grid;
  grid-template-columns: 132px repeat(12, minmax(22px, 1fr));
  gap: 3px;
  margin-top: 4px;
  font-size: 9.5px;
  color: var(--bf-dim);
  text-align: center;
  font-variant-numeric: tabular-nums;
  width: max-content;
  min-width: 100%;
}

/* ── «Hay que mirar esto»: lo que el panorama encontró solo ─────────────────
   El LED toma el color del aviso (M-2, nunca va solo) y se alinea con la
   PRIMERA LÍNEA del bloque (M-10). El texto se queda en tinta normal: teñirlo
   entero convertiría tres avisos en tres carteles. */

.bf-alerts {
  display: flex;
  flex-direction: column;
  gap: 11px;
}

.bf-alert {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

.bf-alert > .bf-led {
  margin-top: 6px;
}

.bf-alert b {
  font-size: 12.5px;
  font-weight: 600;
}

/* El LED es luz, no letra: color gráfico del estado (S-7, chore 90.257). */
.bf-alert.bad > .bf-led {
  color: var(--bf-danger);
}

.bf-alert.warn > .bf-led {
  color: var(--bf-warning);
}

.bf-alert.info > .bf-led {
  color: var(--bf-info);
}

.bf-alert.ok > .bf-led {
  color: var(--bf-success);
}

/* ── Ranking de personas ────────────────────────────────────────────────────
   La barra compara con QUIEN MÁS tiene, no con el total: lo que se lee es la
   distancia entre ellos. */

.bf-rank {
  display: flex;
  flex-direction: column;
  gap: 9px;
}

.bf-rank .row {
  display: flex;
  align-items: center;
  gap: 9px;
  color: inherit;
  text-decoration: none;
}

.bf-rank .row:hover {
  text-decoration: none;
}

.bf-rank .row:hover .who b {
  text-decoration: underline;
}

.bf-rank .bf-av {
  width: 24px;
  height: 24px;
  font-size: 9.5px;
}

.bf-rank .who {
  flex: 1;
  min-width: 0;
}

.bf-rank .who b {
  display: block;
  font-size: 12px;
  font-weight: 400;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Separada del nombre lo bastante para no leerse como su subrayado: son dos
   cosas distintas, y el subrayado de verdad aparece al pasar por encima. */
.bf-rank .track {
  display: block;
  height: 4px;
  margin-top: 5px;
  border-radius: 2px;
  background: var(--bf-border);
}

.bf-rank .track i {
  display: block;
  height: 100%;
  border-radius: 2px;
  background: var(--bf-accent);
}

.bf-rank .val {
  font-size: 11.5px;
  color: var(--bf-muted);
}

/* ═══ LA PÁGINA 404 DE HUGIN (chore 90.610) ═══════════════════════════════════
   La de Hugin (src/front/Pages/NotFound.cshtml y el bloque «404» de manage.css),
   pieza a pieza y con sus tokens traducidos: --panel-color es --bf-surface-bg,
   --hairline-color es --bf-border, --highlight-color es --bf-accent y su canto,
   --bf-accent-edge. La rejilla con un hueco ES el mensaje: «esto no está» se
   dibuja como el sitio donde debería estar y no está. La pinta Ui/BifrostNotFoundPage.

   LA VISTA ENTERA, MEDIDA POR FLEX Y NO POR RESTA, como en Hugin: el cuerpo del
   shell ya es una columna que llena el alto, así que la caja de página se lleva
   lo que quede y la 404, lo que quede de ella. Restar la cabecera y las migas
   sería escribir aquí la altura de otra pieza. La página sin acceso, que desde
   el 2026-10-27 pinta el <bifrost-no-access> del shell, se mide igual: va por
   su clase `bf-nf-page` y no por el nombre del elemento para que la regla siga
   fuera de Dragsa.Design (extraer.mjs descarta por prefijo de clase, `bf-nf`). */
.bf-pagebox:has(> .bf-nf),
.bf-pagebox:has(> .bf-nf-page) {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

/* Mobile-first (MF-2): la base es la del teléfono —menos aire por los cantos,
   porque la nota se parte en tres renglones y las puertas en dos filas— y el
   corte de 620 (MF-3; Hugin lo hace a 48rem, que aquí no es un corte) devuelve el
   relleno de escritorio. Ancho entero y DESPUÉS el tope: con `margin: 0 auto` a
   secas, el hijo de una columna flexible deja de estirarse y las cuatro puertas
   no caben en una fila. */
.bf-nf {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  max-width: 44rem;
  min-height: 0;
  margin: 0 auto;
  padding: 0.5rem 0.5rem 1rem;
  text-align: center;
}

/* El bloque va ENTERO y un pelo por encima del centro: los dos huecos crecen 2 y
   3, así que el grupo se queda al 40 % de lo que sobra —el centro óptico— y se
   encogen a cero cuando no sobra nada. */
.bf-nf::before,
.bf-nf::after {
  content: "";
  flex-shrink: 1;
  flex-basis: 0;
}

.bf-nf::before {
  flex-grow: 2;
}

.bf-nf::after {
  flex-grow: 3;
}

/* El dibujo no crece pero sí encoge: su rejilla se mide en alto de la vista. */
.bf-nf-art {
  display: grid;
  flex: 0 1 auto;
  place-items: center;
  margin-bottom: 0.375rem;
}

/* La celda crece con el ALTO de la vista y no con el ancho, con topes por los dos
   lados: en una pantalla baja no se come las salidas y en una de sala no se
   convierte en un cartel. El alto es el de la vista visible (`dvh`, MF-6), con `vh`
   delante como respaldo. */
.bf-nf-grid {
  display: grid;
  grid-template-columns: repeat(3, clamp(3.25rem, 10.4vh, 7.5rem));
  grid-template-columns: repeat(3, clamp(3.25rem, 10.4dvh, 7.5rem));
  grid-auto-rows: clamp(2rem, 7vh, 5rem);
  grid-auto-rows: clamp(2rem, 7dvh, 5rem);
  gap: clamp(0.3rem, 1vh, 0.7rem);
  gap: clamp(0.3rem, 1dvh, 0.7rem);
}

.bf-nf-grid > span {
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-input);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 60%, transparent);
}

/* Las de alrededor, a media luz y descendiendo hacia los cantos: el hueco es lo
   único que tiene que mirarse. */
.bf-nf-grid > span:nth-child(2n) {
  opacity: 0.55;
}

.bf-nf-grid > span:nth-child(3n) {
  opacity: 0.35;
}

.bf-nf-grid > .bf-nf-hole {
  display: grid;
  place-items: center;
  opacity: 1;
  border-style: dashed;
  border-color: color-mix(in srgb, var(--bf-accent-edge) 55%, transparent);
  background-color: color-mix(in srgb, var(--bf-accent) 8%, transparent);
  font-size: clamp(var(--bf-text-body), 3.4vh, 2rem);
  font-size: clamp(var(--bf-text-body), 3.4dvh, 2rem);
  font-weight: 700;
  letter-spacing: 0.04em;
  color: var(--bf-accent);
}

.bf-nf-title {
  font-size: var(--bf-text-head);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.bf-nf-note {
  max-width: 32rem;
  font-size: var(--bf-text-body);
  line-height: 1.5;
  color: var(--bf-muted);
}

/* Las PUERTAS: nadie llega aquí queriendo, y lo que necesita es salir por donde
   iba, no un botón que le devuelva al principio de todo. */
.bf-nf-doors {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(9.5rem, 100%), 1fr));
  gap: 0.5rem;
  width: 100%;
  margin-top: 1.25rem;
}

.bf-nf-door {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.125rem;
  padding: 0.75rem 1rem;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-modal);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 45%, transparent);
  color: var(--bf-surface-fg);
  text-align: left;
  text-decoration: none;
  transition:
    border-color var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-nf-door > svg {
  width: 1.15rem;
  height: 1.15rem;
  margin-bottom: 0.375rem;
  color: var(--bf-accent);
}

.bf-nf-door-name {
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-nf-door-note {
  font-size: var(--bf-text-label);
  line-height: 1.35;
  color: var(--bf-muted);
}

.bf-nf-door:hover {
  border-color: color-mix(in srgb, var(--bf-accent-edge) 55%, transparent);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 75%, transparent);
  text-decoration: none;
}

/* La pista del menú global. En el teléfono NO sale: dice «pulsa Ctrl + K», que
   ahí no se puede hacer; el buscador sigue en la cabecera, con su lupa. */
.bf-nf-tip {
  display: none;
  margin-top: 1rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-kbd {
  padding: 0.0625rem 0.375rem;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-xs);
  background-color: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  font-family: inherit;
  font-size: var(--bf-text-mini);
  line-height: 1.35;
  color: inherit;
}

@media (min-width: 620px) {
  .bf-nf {
    padding: 1rem 1rem 1.5rem;
  }

  .bf-nf-tip {
    display: block;
  }
}

/* POR DEBAJO DE 600 px DE ALTO, EL DIBUJO SE VA: la rejilla ya está en su medida
   mínima y no caben las cuatro salidas, que son a lo que se viene. En el teléfono
   el listón sube a 700. EXCEPCIÓN A MF-2 anotada: es una consulta de ALTO, que el
   «móvil primero» no ensancha; el `max-width` acompaña al alto y es el corte
   compacto de MF-3 (el mismo 619.98 que usa el shell). */
@media (max-height: 600px), (max-width: 619.98px) and (max-height: 700px) {
  .bf-nf-art {
    display: none;
  }
}

/* ═══ 12 · Piezas heredadas, aún en uso ══════════════════════════════════════
   Lo que las pantallas actuales emiten y que el rediseño irá retirando pantalla
   a pantalla (F1…F3 del estudio). Se re-viste con los tokens nuevos para que
   nada quede a medio pintar entre esta chore y la suya. No usar en piezas nuevas.

   El barrido final (U-22 · chore 90.081) YA PASÓ por aquí y borró lo que había
   dejado de emitir alguien: `.bf-button` y sus modificadores `.is-*`, `.bf-cards`,
   `.bf-message`, `.bf-checkbox`, `.bf-actions`, `.bf-panel-header`, `.group > h2`
   y `.status[data-alive|data-license]`. Lo que sigue aquí NO es residuo: cada
   regla tiene hoy quien la escriba —búscala con grep antes de tocarla—. Las que
   más despistan son las sin prefijo: `.usermenu*` la emite
   `Portal/PortalPage.cs` (y su suite la fija), y `.group` la escribe la vista
   previa del menú de un rol. Las `.notif*` del panel de avisos del Inicio se
   fueron con él: la campana vive en la cabecera del shell y la lista, en la
   pantalla de avisos (`.bf-notice*`). */

/* Envoltorio de contenido de página. Antes se llamaba .bf-page; se renombró en
   esta chore porque .bf-page es, en el sistema Bifrost-3, la PAGINACIÓN del pie
   de tabla. Desaparece con U-3 (chore 90.062).

   Mobile-first (MF-2 · chore 90.099): la base es el relleno de TELÉFONO y el
   corte de 620 devuelve el 18/26 de la maqueta. Los 26+26 de escritorio son la
   mitad del problema de ancho en un móvil: la página va DENTRO del cuerpo del
   shell, que ya pone sus 16 de aire, así que a 360 px el contenido se quedaba
   sin 84 px de los 360.

   El `width: 100%` y el `min-width: 0` no son cosmética: el shell proyecta esta
   caja en un flex en columna, y ahí el `margin: 0 auto` que la centra en
   escritorio ANULA el estirado —una caja con margen automático en el eje
   transversal no se estira—, así que la caja se dimensionaba por su contenido
   MÍNIMO. Bastaba un control que no sabe encoger —un `input[type=file]`, cuyo
   mínimo son sus ~306 px de botón y nombre de fichero— para que la página
   entera se plantara más ancha que la pantalla y se desplazara en horizontal
   (MF-5, visto en «Tu perfil» a 360 px). Con el ancho declarado, el centrado y
   el tope de 76rem siguen valiendo donde sobra sitio. */
/* A TODO LO ANCHO, como las vistas de Hugin: el margen lo pone el cuerpo del shell (el --page-pad de
   Hugin), así que la caja de página ya no suma otro relleno ni se centra en 76 rem. */
.bf-pagebox {
  width: 100%;
  min-width: 0;
  margin: 0;
  padding: 0;
}

/* LA CABECERA DE PANTALLA DE LATANUR (`.page-head`, chore 90.394): título a la
   izquierda, mandos empujados al canto derecho y, si hay pista, en su propio
   renglón DEBAJO del título —al lado competía con él y era lo primero en cortarse—.
   El orden lo pone el CSS para no tocar las vistas, que escriben la pista antes o
   después de los mandos según la pantalla. */
.bf-page-header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.bf-page-header h1 {
  margin: 0;
  font-size: var(--bf-text-head);
  font-weight: 600;
  line-height: 1.2;
  /* Como el título de página de Hugin: sin el apretado del h1 global (chore 90.558). */
  letter-spacing: normal;
}

/* El renglón ENTERO y la medida por dentro (chore 90.486): con `max-width: 68ch` la base del 100 % se recortaba a la
   medida y, con un título corto, la pista cabía a su lado. El relleno derecho es lo que pase de 68ch, la medida de
   .page-hint en Hugin (chore 90.558). */
.bf-page-header > p {
  order: 3;
  flex: 1 0 100%;
  max-width: none;
  padding-right: max(0px, 100% - 68ch);
  margin-top: -0.25rem;
  font-size: var(--bf-text-label);
  line-height: 1.45;
  color: var(--bf-muted);
}

/* Acciones de la cabecera de página (chore 90.072). En la maqueta viven arriba a
   la derecha del título, no debajo de la tabla: lo que se hace SOBRE la lista se
   ofrece antes de leerla. Envuelven porque son lo primero que sobra a 360 px. */
.bf-page-header .acts {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-left: auto;
}

/* LA VUELTA, la de Hugin (su .btn-back, chore 90.430): delante del título, con el alto de un mando, su galón y la
   palabra; la tinta apagada y el velo al apuntar. Era un botón con canto a la derecha, lo último de la cabecera, y la
   salida de una pantalla no puede ser lo que menos se ve. El margen negativo la alinea con el canto del contenido
   por la punta del galón, no por la caja. Acotada a la cabecera (chore 90.431): «.bf-back» a secas es el pie de las
   puertas de sesión (el «Volver al inicio de sesión», más arriba), y esta regla lo descentraba. */
.bf-page-header .bf-back {
  display: inline-flex;
  flex-shrink: 0;
  align-items: center;
  gap: 0.25rem;
  height: 1.9rem;
  margin-left: -0.5rem;
  padding: 0 0.75rem 0 0.5rem;
  border-radius: 999px;
  font-size: var(--bf-text-ui);
  font-weight: 600;
  color: var(--bf-muted);
  text-decoration: none;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-page-header .bf-back > svg {
  width: 0.9rem;
  height: 0.9rem;
}

.bf-page-header .bf-back:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
  text-decoration: none;
}

.bf-page-header .bf-back:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
}

/* Caja de las páginas de sesión heredadas: la .bf-centered nueva espera un
   .box; estas emiten un .bf-panel. */
.bf-panel {
  background: var(--bf-surface-bg);
  color: var(--bf-surface-fg);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
  padding: var(--bf-panel-padding);
  font-size: var(--bf-panel-font-size);
}

.bf-centered .bf-panel {
  width: 100%;
  max-width: 26rem;
}

/* Las vistas heredadas apilan bloques sueltos (título, tabla, formulario) dentro
   del panel y contaban con los márgenes que el sistema nuevo pone a cero: aquí se
   les devuelve el aire con la separación del catálogo. */
.bf-panel > * + * {
  margin-top: 14px;
}

.bf-panel > h1,
.bf-panel > h2,
.bf-panel > h3,
.bf-pagebox > h1,
.bf-pagebox > h2 {
  margin-bottom: 12px;
}

.bf-muted {
  color: var(--bf-muted);
  font-size: var(--bf-legend-font-size);
}

.bf-stack {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.bf-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 9px;
}

.bf-spacer {
  flex: 1 1 auto;
}

/* ── Página con columna de apoyo (chore 90.066 · U-7) ───────────────────────
   La reja de la maqueta para una página de shell: lo que se hace a la izquierda
   y las tarjetas que lo acompañan —estado, avisos— a la derecha. Mobile-first
   (MF-2): la regla base es UNA columna (en un teléfono la columna de apoyo va
   debajo, que es donde se lee después) y el corte canónico de 900 (MF-3) la
   pone al lado con los 290 px de la maqueta. */
.bf-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 20px;
  /* Cada columna mide lo que mide su contenido: sin esto la rejilla estira la
     tarjeta corta hasta la altura de la otra y deja media caja vacía. */
  align-items: start;
}

.bf-split > .side {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

@media (min-width: 900px) {
  .bf-split {
    grid-template-columns: minmax(0, 1fr) 290px;
  }
}

/* Pasos numerados en vertical (alta del segundo factor 04.001). La numeración
   la pone el CSS con un contador: es una secuencia real, y así no puede
   descuadrarse al añadir o quitar un paso desde la página. Distinto de
   .bf-steps, que es la línea horizontal del asistente. */
.bf-steplist {
  list-style: none;
  counter-reset: bf-step;
  display: flex;
  flex-direction: column;
  gap: 16px;
  margin: 0;
  padding: 0;
}

/* Lo que va DEBAJO de la lista dentro de la tarjeta —hoy el botón «Empezar la
   configuración» del estado sin activar— no tenía nada que lo separase: `.in`
   es sólo relleno, `.bf-steplist` declara `margin: 0` y el `gap: 12px` de
   `.bf-form.inline` es entre sus PROPIOS hijos, no con lo que tiene encima. Cero
   píxeles, o sea que el botón quedaba MÁS pegado al tercer paso que los pasos
   entre sí (chore 90.135, avisada por el propietario). Se separa con los mismos
   16 px del `gap` de la lista, para que el botón caiga en el ritmo que ya lleva
   la pantalla y no invente un tercer hueco.
   Sólo alcanza al estado sin activar: en el de alta el formulario va DENTRO de
   un `<li>`, así que no es hermano de la lista y no lo toca esta regla. */
.bf-steplist + * {
  margin-top: 16px;
}

/* Mobile-first (MF-2 · chore 90.099): en un teléfono el disco NO se lleva una
   columna propia. El sangrado de la numeración (22 + 12) sale del ancho del paso,
   y el contenido del paso 3 son las seis casillas del código, que ya iban justas.
   Flotado, el disco queda a la izquierda del TÍTULO —que es donde lo pone la
   maqueta— y todo lo que cae por debajo de sus 22 px (la pista, la clave manual,
   el código) recupera el ancho entero. Desde 620 vuelve la reja de dos columnas. */
.bf-steplist > li {
  display: block;
}

.bf-steplist > li::before {
  float: left;
  margin-right: 12px;
}

/* Un contenedor flex NO se solapa con el flotado: lo esquiva encogiéndose, y ahí
   se perderían los 34 px que este bloque venía a recuperar. El `clear` va en el
   FORMULARIO —la caja de bloque del paso— y no en `.bf-code`: `clear` sólo lo
   atienden las cajas de bloque, y un ítem flex (que es lo que `.bf-code` es
   dentro del formulario) lo ignora. */
.bf-steplist .bf-form {
  clear: left;
}

@media (min-width: 620px) {
  .bf-steplist > li {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr);
    gap: 12px;
    align-items: start;
  }

  .bf-steplist > li::before {
    float: none;
    margin-right: 0;
  }
}

.bf-steplist > li::before {
  counter-increment: bf-step;
  content: counter(bf-step);
  width: 22px;
  height: 22px;
  border-radius: 50%;
  border: 1px solid var(--bf-border);
  color: var(--bf-dim);
  background: var(--bf-surface-bg);
  font-size: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Paso HECHO: disco verde. El número va del color del FONDO de superficie
   —blanco en claro, pizarra en oscuro— porque el verde del tema oscuro es
   claro y el blanco fijo de la maqueta se quedaría sin contraste sobre él. */
.bf-steplist > li.done::before {
  background: var(--bf-success);
  border-color: transparent;
  color: var(--bf-surface-bg);
}

/* Paso ACTUAL: disco de acento. Sobre naranja el texto es siempre blanco (M-2). */
.bf-steplist > li.now::before {
  background: var(--bf-accent);
  border-color: transparent;
  color: var(--bf-accent-fg);
}

.bf-steplist b {
  font-size: var(--bf-panel-font-size);
  display: block;
}

/* Cuerpo del alta: los pasos y el QR. Sin media query (MF-2): el QR se pone al
   lado cuando cabe y baja solo cuando no. */
.bf-enroll {
  display: flex;
  flex-wrap: wrap;
  gap: 26px;
}

.bf-enroll > .steps {
  flex: 1 1 18rem;
  min-width: 0;
}

.bf-enroll > .bf-qr {
  flex: 0 1 236px;
}

.bf-qr {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}

/* El QR se dibuja SIEMPRE sobre blanco y con su zona de silencio, en los dos
   temas: no es decoración, es lo que tiene que leer la cámara de un teléfono, y
   un QR oscuro sobre fondo oscuro no se escanea. Es la misma licencia que se
   toma la maqueta. Por eso su blanco se queda escrito: ni el tema ni la marca
   deben tocarlo. */
.bf-qr img {
  width: 200px;
  max-width: 100%;
  height: auto;
  background: #ffffff;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-l);
  padding: 12px;
  image-rendering: pixelated;
}

.bf-qr .bf-hint {
  text-align: center;
}

/* Formulario que ACOMPAÑA a otro contenido (un paso de una lista, una tarjeta)
   en vez de ser la columna entera de una pantalla de acceso: su botón abraza el
   texto —como en la maqueta— en lugar de estirarse hasta el borde, y el hueco
   es el corto. */
.bf-form.inline {
  align-items: flex-start;
  gap: 12px;
}

/* Única excepción al centrado de las casillas (90.222): cuando la entrada del código
   ACOMPAÑA a un paso —el alta del segundo factor— su columna crece con la tarjeta,
   y unas casillas centradas se despegarían del rótulo del paso que tienen encima y
   del botón de confirmar que tienen debajo, los dos pegados a la izquierda como en
   la maqueta. Aquí el borde izquierdo es la alineación, no el centro. */
.bf-form.inline .bf-code {
  justify-content: flex-start;
}

/* Clave manual del alta: la que se teclea cuando no se puede escanear. */
.bf-key {
  display: inline-block;
  padding: 7px 11px;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-s);
  background: var(--bf-panel-bg);
  color: var(--bf-surface-fg);
  font-family: var(--bf-font-mono);
  font-size: 13px;
  letter-spacing: 0.12em;
  word-break: break-all;
}

/* Menú de usuario del portal (12.004). */
.usermenu {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
}

.usermenu-lang,
.usermenu-theme {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  margin: 0;
}

.usermenu .usermenu-item[aria-current="true"] {
  background: var(--bf-accent);
  border-color: var(--bf-accent);
  color: var(--bf-accent-fg);
}

/* Grupo de una lista agrupada. Nació como el grupo de producto de la rejilla del
   portal —que ya no pinta los servicios: la chore 90.705 los quitó del
   Inicio—, pero el barrido de U-22 (chore 90.081) NO lo borra: la
   clase la escribe hoy la vista previa del menú de un rol (`.bf-menupv > li.group`,
   ProductRoles/Permissions), que además la busca desde su módulo TS. */
.group {
  margin: 1.5rem 0;
}

/* ── Centro de avisos: la pantalla (chore 90.069 · U-11; vestida en la 90.617) ─
   Era una lista de filas con filete, todas del mismo peso: lo leído sólo bajaba
   de opacidad entero —y con él su texto, hasta costar leerlo— y lo nuevo no
   llamaba. Vestida con el lenguaje de la ficha de instancia (90.596) y de «Mis
   sesiones» (90.616) —y sólo vestida: avisos, orden, filtros y acciones son los
   de antes—:
     · la ficha lleva la ficha del glifo y el bisel (`.bf-ipanel`), y arriba el
       estado de la bandeja en baldosas hundidas (`.bf-istats`);
     · los avisos van agrupados por antigüedad, cada tramo con su rótulo y su
       cuenta, en tarjetas hundidas (M-5);
     · cada aviso lleva la baldosa de su TIPO teñida de su severidad, con el LED
       en la esquina y el tipo escrito debajo (M-2);
     · lo SIN LEER tiene presencia —el filo y la luz del acento por la izquierda
       y el título con todo su peso—, y lo LEÍDO se queda en voz baja sin dejar
       de leerse: baja la tinta, no la opacidad de la tarjeta entera;
     · en un puesto con ratón, las acciones de la fila se encienden al apuntarla.
   Mobile-first (MF-2): la tarjeta NACE apilada, con las acciones bajo el cuerpo;
   desde el corte medio (MF-3) se van a su columna de la derecha. */
.bf-notices {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 16px;
}

.bf-notices-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 1rem;
}

@media (min-width: 620px) {
  .bf-notices-body {
    padding: 1.125rem 1.25rem 1.25rem;
  }
}

.bf-notices-body > .bf-isub {
  margin-top: 0.375rem;
  margin-bottom: -0.125rem;
}

/* La cuenta del tramo, en su pastilla neutra antes del filete. */
.bf-notices-body > .bf-isub > .n {
  display: inline-grid;
  place-items: center;
  min-width: 1.375rem;
  height: 1.25rem;
  margin-left: -0.375rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  font-size: var(--bf-text-mini);
  font-variant-numeric: tabular-nums;
  color: var(--bf-muted);
}

/* Las tres cifras de la bandeja subieron a la cabecera, en cápsulas (chore 90.711). */

/* Todo leído: la respuesta buena, con el visto en el verde de lo que está bien.
   Centrada, como todo vacío (chore 90.682). */
.bf-notices-allread {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.625rem;
  margin: 0;
  padding: 0.75rem 1rem;
  border-radius: var(--bf-radius-btn);
  background: color-mix(in srgb, var(--bf-success) 9%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-success) 22%, transparent);
  font-size: var(--bf-text-ui);
  color: var(--bf-surface-fg);
}

.bf-notices-allread > svg {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--bf-success-text);
}

.bf-notice-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ── La tarjeta de un aviso ── */
.bf-notice {
  --nt-tone: var(--bf-off);
  position: relative;
  isolation: isolate;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "tile main time"
    ". acts acts";
  align-items: start;
  gap: 0.625rem 0.75rem;
  padding: 0.875rem 1rem;
  border-radius: var(--bf-radius-btn);
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-border);
  transform-origin: 50% 0;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    box-shadow var(--bf-dur-fast) var(--bf-ease-out);
  animation: bf-ipanel-in var(--bf-dur-side) var(--bf-ease-pop) backwards;
  animation-delay: calc(var(--bf-dur-fast) + min(var(--i, 0), 10) * var(--bf-dur-fast) / 3);
}

.bf-notice[data-tone="bad"] {
  --nt-tone: var(--bf-danger);
}

.bf-notice[data-tone="warn"] {
  --nt-tone: var(--bf-warning);
}

.bf-notice[data-tone="info"] {
  --nt-tone: var(--bf-info);
}

.bf-notice:hover {
  box-shadow: inset 0 0 0 1px var(--bf-border-strong);
}

/* SIN LEER: la luz del acento nace por la izquierda; sin filo en el canto (chore 90.626): lo dicen su punto y su texto. */
.bf-notice[data-read="false"] {
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--bf-accent) 9%, transparent), transparent 55%),
    var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 22%, var(--bf-border));
}

/* La baldosa del tipo, teñida de su severidad; el LED en la esquina, recortado. */
.bf-notice-tile {
  grid-area: tile;
  position: relative;
  display: grid;
  place-items: center;
  width: 2.375rem;
  height: 2.375rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--nt-tone) 15%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--nt-tone) 24%, transparent);
  color: color-mix(in srgb, var(--nt-tone) 72%, var(--bf-surface-fg));
}

.bf-notice-tile > svg {
  width: 1.15rem;
  height: 1.15rem;
}

.bf-notice-tile > .bf-led {
  position: absolute;
  right: -0.1875rem;
  bottom: -0.1875rem;
  width: 0.5625rem;
  height: 0.5625rem;
  box-shadow: 0 0 0 2px var(--bf-panel-bg);
}

.bf-notice-main {
  grid-area: main;
  min-width: 0;
}

.bf-notice-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.625rem;
}

.bf-notice-title {
  font-size: var(--bf-text-body);
  font-weight: 600;
  line-height: 1.35;
  color: var(--bf-surface-fg);
  overflow-wrap: anywhere;
}

a.bf-notice-title {
  text-decoration: none;
}

a.bf-notice-title:hover {
  color: var(--bf-accent-text);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

/* La hora, en su celda: arriba a la derecha, a la altura del título y a la misma en
   todas las tarjetas; en línea, además, encima de las acciones. */
.bf-notice > time {
  grid-area: time;
  align-self: start;
  justify-self: end;
  line-height: 1.35rem;
  font-size: var(--bf-text-label);
  font-variant-numeric: tabular-nums;
  color: var(--bf-dim);
  white-space: nowrap;
}

.bf-notice-body {
  margin-top: 0.25rem;
  font-size: var(--bf-text-ui);
  line-height: 1.5;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
}

/* La procedencia, bajo el cuerpo: el tipo ESCRITO (M-2) y el origen etiquetado. */
.bf-notice-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem 0.5rem;
  margin-top: 0.5rem;
}

.bf-notice-kind {
  font-size: var(--bf-text-mini);
  font-weight: 600;
  color: var(--bf-muted);
}

/* LEÍDO: en voz baja, pero legible. Baja la tinta y la baldosa pierde el tono —el
   LED lo conserva, con el tipo escrito al lado—; la tarjeta no se apaga entera. */
.bf-notice[data-read="true"] .bf-notice-title {
  font-weight: 500;
  color: var(--bf-muted);
}

.bf-notice[data-read="true"] .bf-notice-body {
  color: var(--bf-dim);
}

.bf-notice[data-read="true"] .bf-notice-tile {
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
  color: var(--bf-dim);
}

/* Las acciones caen bajo el cuerpo mientras la tarjeta está apilada, alineadas con
   él (segunda columna) para que no queden colgando bajo la baldosa. Son dos desde
   la chore 90.364 —la de lectura y «Quitar»— y van juntas en su caja. */
.bf-notice-acts {
  grid-area: acts;
  justify-self: start;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
}

@media (min-width: 620px) {
  .bf-notice {
    grid-template-rows: auto 1fr;
    grid-template-areas:
      "tile main time"
      "tile main acts";
    row-gap: 0.5rem;
  }

  .bf-notice-acts {
    align-self: end;
    justify-self: end;
  }
}

/* Con ratón, las acciones de la fila se encienden al apuntarla o al llegar con el
   tabulador; nunca se esconden del todo, para que se sepa que están. */
@media (hover: hover) and (min-width: 620px) {
  .bf-notice-acts {
    opacity: 0.55;
    transition: opacity var(--bf-dur-fast) var(--bf-ease-out);
  }

  .bf-notice:hover .bf-notice-acts,
  .bf-notice:focus-within .bf-notice-acts {
    opacity: 1;
  }
}

/* La barra de filtros de Avisos en el teléfono (chore 90.691): las pastillas se repartían el renglón a su ancho y
   dejaban un hueco a la derecha, y el recuento caía solo, pegado al borde contrario. Aquí cada filtro se estira
   hasta llenar su renglón y el recuento se lee a la izquierda, bajo ellos, como el resto de la página. */
/* Excepción a MF-2: se escribe como max-width porque solo corrige el teléfono; la barra de escritorio es la de
   todas las pantallas (`.bf-toolbar`) y no se reescribe para una sola. */
@media (max-width: 619.98px) {
  form.bf-toolbar[action="/portal/notifications"] > .bf-filter { flex: 1 1 8rem; }
  form.bf-toolbar[action="/portal/notifications"] > .bf-filter > select { flex: 1; min-width: 0; }
  form.bf-toolbar[action="/portal/notifications"] > .count { flex-basis: 100%; margin-left: 0; }

  /* Con `base-select` (Chrome), el suelo de 8 rem de los desplegables de filtro pesa más que el `min-width: 0`
     de arriba: a 375 px el de «Producto» se salía de su pastilla y desplazaba la página (MF-5). En el teléfono
     cada filtro ya llena su renglón, así que el desplegable mide lo que le deja la pastilla. Mismo peso que
     aquel suelo más un elemento, para ganarle. */
  html:root form.bf-toolbar[action="/portal/notifications"] > .bf-filter > select:not([multiple]):not([size]) {
    min-inline-size: 0;
  }
}

.bf-notices-card > .bf-foot {
  flex-wrap: nowrap;
  align-items: flex-start;
  line-height: 1.5;
}

.bf-notices-card > .bf-foot > svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  margin-top: 0.1875rem;
}

@media (prefers-reduced-motion: reduce) {
  .bf-notice,
  .bf-notice-acts {
    animation: none;
    transition: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-notice, .bf-notice-acts) {
  animation: none;
  transition: none;
}

/* ── Mi perfil: la pieza de la foto (chore 90.134) ────────────────────────────
   Lo que sigue lo comparten «Mi perfil» y la cabecera del expediente de una
   persona en la administración (`.bf-rechead`): el formulario de la imagen, el
   nombre del fichero elegido y el retrato que hace de zona de subida. La
   DISPOSICIÓN de «Mi perfil» —portada y paneles, como Latanur— va en su propio
   bloque, justo debajo de éste. */

/* Cambiar la imagen: elegir fichero, verlo y enviarlo son UNA acción y van juntos
   (chore 90.134). La pieza tiene DOS caras y el catálogo decide cuál se ve según
   la marca `data-hydrated` que pone el módulo, igual que el modo reordenar del
   árbol de categorías:
     · hidratada — lo que dibuja la maqueta: un botón del catálogo. El selector
       nativo se aparta (sigue accesible: 1×1 y transparente, misma técnica que
       `.bf-seg label input`) y la etiqueta `for` es quien abre el diálogo.
     · sin hidratar — el selector nativo, que sin JavaScript es la única forma de
       elegir un fichero; el botón se esconde porque no habría quien enseñara la
       vista previa ni el nombre elegido. */
.bf-idcard-image {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  min-width: 0;
}

.bf-idcard-image[data-hydrated] input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
  padding: 0;
  border: 0;
  pointer-events: none;
}

.bf-idcard-image:not([data-hydrated]) [data-image-pick] {
  display: none;
}

/* Que el selector NO crezca: pide de suyo bastante más de lo que ocupa el botón
   que la maqueta dibuja en su lugar, y estirándolo se comía la fila de acciones.
   Encoge cuando falta sitio y envuelve en pantalla estrecha. */
.bf-idcard-image:not([data-hydrated]) input.bf-input[type="file"] {
  flex: 0 1 auto;
  width: auto;
  min-width: 0;
  max-width: 100%;
}

/* El foco del teclado tiene que verse en el BOTÓN, porque el campo no se ve
   (mismo criterio que `.bf-seg label`). */
.bf-idcard-image:has(input[type="file"]:focus-visible) [data-image-pick] {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
  /* La tinta del anillo doble (90.147) se la queda el `input[type=file]`, que
     está escondido: aquí se repite sobre el botón que sí se ve. */
  box-shadow: 0 0 0 1px var(--bf-surface-fg);
}

/* Qué fichero se ha elegido, o por qué no vale. Vacío no ocupa: la fila de
   acciones vuelve a ser la de la maqueta mientras no hay nada que decir. */
.bf-idcard-file {
  font-size: 12.5px;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
  min-width: 0;
}

.bf-idcard-file:empty {
  display: none;
}

.bf-idcard-file.err {
  color: var(--bf-danger-text);
}

/* El avatar es la zona de subida cuando hay JavaScript: se pulsa y se le suelta
   un fichero encima. El aro sólo aparece mientras se arrastra algo. */
.bf-idcard-face {
  display: inline-flex;
  border-radius: 50%;
}

/* Sólo se puede pulsar cuando hay quien atienda: el avatar es hermano MAYOR de la
   fila de acciones, así que la marca se lee desde la tarjeta que los contiene. */
.bf-idcard:has(.bf-idcard-image[data-hydrated]) .bf-idcard-face,
.bf-rechead:has(.bf-idcard-image[data-hydrated]) .bf-idcard-face {
  cursor: pointer;
}

.bf-idcard-face[data-drop] {
  outline: 2px dashed var(--bf-accent);
  outline-offset: 3px;
}

/* ── Mi perfil: LA FICHA DE LATANUR (rediseño general, 2026-09-28) ─────────────
   Por decisión del propietario, «Mi perfil» tiene la disposición y el vestido del
   perfil de Latanur (`src/front/css/components/profile.css`, «LA FICHA DE PERFIL»,
   01.020), con sus medidas copiadas y sus tokens traducidos a los `--bf-*`:
     card-surface → --bf-card-bg          highlight → --bf-accent
     action       → --bf-info             panel-2   → --bf-control-bg
     elevated     → --bf-elevated-bg      control-hover → --bf-control-hover-bg
     border       → --bf-border           radius-card   → --bf-radius-modal
     text / muted → --bf-surface-fg / --bf-muted
   Es la única pantalla que habla DE QUIEN la mira, y lo que la hace reconocible es
   la PORTADA: el retrato grande pisando una banda de color, el nombre a tamaño de
   titular y el papel que juega. Debajo, dos paneles de «rótulo y valor» que se leen
   de un vistazo. La diferencia con Latanur es de función, no de forma: aquí la
   ficha ES el formulario (F-1), así que el par «Descartar / Guardar cambios» ocupa
   en la portada el sitio del «Editar» de allí y el nombre completo es un campo
   dentro de su renglón.
   Mobile-first (MF-2) sin una sola media query: la portada envuelve (el nombre baja
   bajo el retrato y las acciones a su propia fila) y los paneles se ponen en
   paralelo sólo cuando caben los dos, con `auto-fit`. */
.bf-prof {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  margin-top: 1rem;
}

/* LA PORTADA. Macizo y sin sombra (M-4): la separa su canto. La banda de color es
   lo único de la casa que se pinta con la marca a lo ancho, y se lo puede permitir
   porque esta pantalla es de una sola persona y no compite con ningún dato. El
   degradado muere antes de la mitad para que el nombre se lea sobre la superficie
   y no sobre el color. */
.bf-prof-hero {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 1.25rem;
  padding: 1.25rem;
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-radius-modal);
  background: var(--bf-card-bg);
}

.bf-prof-hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 7.5rem;
  background: linear-gradient(
    160deg,
    color-mix(in srgb, var(--bf-accent) 22%, transparent),
    color-mix(in srgb, var(--bf-info) 14%, transparent) 60%,
    transparent);
  /* La banda se FUNDE hacia abajo (chore 90.691): acababa en un canto duro que a ancho de escritorio cruzaba el
     nombre y el correo por la mitad. Fundida, el retrato sigue pisándola y el texto no queda partido. El #000 de la
     máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: linear-gradient(to bottom, #000 45%, transparent);
  mask-image: linear-gradient(to bottom, #000 45%, transparent);
}

/* El retrato PISA la banda: por eso la portada es alta y el disco baja hasta cruzarla. */
.bf-prof-face {
  position: relative;
  flex: none;
  margin-top: 2.25rem;
}

/* SIN SOMBRA (Latanur): lo que separa el retrato de la banda es su ARO de 3 px del
   color de la portada, un corte limpio. Las iniciales, a pelo sobre el gris del
   control: no son un hueco, son lo que hay. Mismas medidas para la foto, que se
   recorta al círculo, y para la vista previa del módulo, que copia la clase. */
.bf-prof-face .bf-av.lg {
  width: 6.5rem;
  height: 6.5rem;
  border: 3px solid var(--bf-card-bg);
  background: var(--bf-control-bg);
  color: var(--bf-muted);
  font-size: 1.9rem;
  font-weight: 600;
  font-style: normal;
  letter-spacing: 0.02em;
}

/* LA CÁMARA encima del retrato, como en cualquier sitio donde se cambia una foto:
   se pulsa lo que se quiere cambiar. Es una etiqueta del selector, así que funciona
   también sin JavaScript. */
.bf-prof-cam {
  position: absolute;
  right: -0.1rem;
  bottom: -0.1rem;
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  cursor: pointer;
  background: var(--bf-elevated-bg);
  color: var(--bf-surface-fg);
  border: 2px solid var(--bf-card-bg);
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-prof-cam:hover {
  background: var(--bf-control-hover-bg);
}

.bf-prof-cam > svg {
  width: 15px;
  height: 15px;
}

/* El foco del selector —que no se ve— se pinta en la cámara, que es lo que se ve
   (mismo criterio que `.bf-seg label`). */
.bf-prof-hero:has(.bf-idcard-image[data-hydrated] input[type="file"]:focus-visible) .bf-prof-cam {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-prof-id {
  position: relative;
  flex: 1 1 14rem;
  min-width: 0;
}

.bf-prof-name {
  font-size: 1.45rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: 0;
  color: var(--bf-surface-fg);
  overflow-wrap: anywhere;
}

.bf-prof-mail {
  font-size: 0.85rem;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
}

.bf-prof-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.5rem;
}

.bf-prof-tags .bf-pill > svg {
  width: 14px;
  height: 14px;
  flex: none;
}

/* La foto en la portada: hidratada, el gesto es la cámara, así que el botón
   «Cambiar imagen» se aparta de la vista —sigue ahí: es el nombre accesible del
   selector— y el formulario sólo asoma con lo elegido y su «Guardar imagen». Sin
   hidratar se queda como en todas partes: el selector nativo con su envío. */
.bf-prof-hero .bf-idcard-image[data-hydrated] [data-image-pick] {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.bf-prof-hero .bf-idcard-image:not([data-hydrated]),
.bf-prof-hero .bf-idcard-image:has([data-image-name]:not(:empty)) {
  margin-top: 0.625rem;
}

.bf-prof-note {
  margin-top: 0.375rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

/* Las acciones, abajo a la derecha y en su propia fila cuando no caben: son de esta
   persona, no de la pantalla. */
.bf-prof-acts {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-left: auto;
}

.bf-prof-acts form {
  display: contents;
}

/* LOS PANELES, UNO BAJO OTRO en todos los anchos (chore 90.406, lo pidió el usuario):
   Identidad y luego Seguridad, cada uno a lo ancho de la vista. */
.bf-prof-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.bf-prof-panel {
  padding: 1rem 1.15rem;
}

.bf-prof-panel-head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin-bottom: 0.35rem;
}

/* El glifo a pelo, en la tinta de marca: tres cuadrados de color pesarían más que
   el rótulo que acompañan (Latanur). */
.bf-prof-panel-ico {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--bf-accent);
}

.bf-prof-panel-ico > svg {
  width: 16px;
  height: 16px;
}

.bf-prof-panel-title {
  font-size: 0.95rem;
  font-weight: 600;
}

.bf-prof-panel-hint {
  font-size: 0.78rem;
  color: var(--bf-muted);
  margin-bottom: 0.75rem;
}

/* Rótulo a la izquierda y valor a la derecha, con filete entre renglones: es como
   se lee una ficha de papel. Los renglones de preferencia (`.bf-srow`) llevan el
   mismo filete y el mismo aire, con su mando al final. */
.bf-prof-list {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.bf-prof-list > * {
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--bf-border);
}

.bf-prof-list > :last-child {
  border-bottom: 0;
  padding-bottom: 0;
}

.bf-prof-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
}

.bf-prof-row dt {
  font-size: 0.8rem;
  color: var(--bf-muted);
}

.bf-prof-row dd {
  margin: 0;
  font-size: 0.9rem;
  color: var(--bf-surface-fg);
  text-align: right;
  overflow-wrap: anywhere;
}

/* El renglón que se escribe: el campo ocupa el sitio del valor y crece hasta un
   ancho de nombre; en el teléfono baja entero bajo su rótulo. */
.bf-prof-row:has(> dd.edit) {
  align-items: center;
}

.bf-prof-row dd.edit {
  flex: 1 1 12rem;
  max-width: 20rem;
}

.bf-prof-row dd.edit .bf-input {
  width: 100%;
}

.bf-prof-list > .bf-srow .bf-srow-main b,
.bf-prof-list > .bf-srow .bf-check b {
  font-size: 0.9rem;
  font-weight: 500;
}

/* Panel «Seguridad» (chore 90.392): el valor del renglón es un estado y su paso
   siguiente, en línea; la contraseña se despliega (details nativo) y el formulario
   baja entero bajo el renglón. Tintas de los tokens: claro y oscuro salen solos. */
.bf-prof-sec {
  align-items: center;
}

.bf-prof-sec > dd {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}

.bf-prof-sec > dd[data-password-external] {
  display: block;
  max-width: 22rem;
  font-size: 0.85rem;
  color: var(--bf-muted);
}

/* El desplegable del renglón (`.bf-prof-fold`): lo comparten la contraseña y,
   desde la chore 90.423, el segundo factor, que dejó de ser una vista aparte. */
.bf-prof-sec > dd.bf-prof-fold {
  flex: 1 1 100%;
  display: block;
}

.bf-prof-fold > details > summary {
  list-style: none;
  margin-left: auto;
  width: max-content;
  cursor: pointer;
}

.bf-prof-fold > details > summary::-webkit-details-marker {
  display: none;
}

.bf-prof-fold > details[open] > summary {
  margin-bottom: 0.75rem;
}

/* Segundo factor (chore 90.423): el estado va en pastilla junto al rótulo, y el
   cuerpo del desplegable apila aviso, cabecera del paso, pasos y formulario con
   el mismo aire que el de la contraseña. Los pasos, la clave, el QR y las casillas
   son las piezas de siempre (`.bf-steplist`, `.bf-enroll`, `.bf-qr`, `.bf-code`):
   aquí sólo se les devuelve la alineación a la izquierda que el renglón (valor a
   la derecha) les quitaría. */
/* La pastilla comparte LÍNEA BASE con el rótulo (chore 90.628): centradas, su letra
   —más pequeña y en negrita— caía un punto por debajo de la del título. */
.bf-prof-sec > dt:has(> .bf-pill) {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

/* EL BOTÓN EN LA LÍNEA DE SU RÓTULO (chore 90.659): el desplegable ocupaba el renglón entero y su botón caía
   debajo, a la derecha, lejos de «Contraseña» o de «Verificación en dos pasos». Ahora rótulo y desplegable comparten
   la MISMA celda de rejilla: el rótulo arriba a la izquierda, el botón (summary) arriba a la derecha y centrados en la
   misma altura, y lo que se despliega baja debajo de los dos a todo el ancho. El rótulo sube a tinta de texto: es el
   nombre de la fila, no un dato secundario. */
.bf-prof-sec:has(> dd.bf-prof-fold) {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}

.bf-prof-sec:has(> dd.bf-prof-fold) > dt,
.bf-prof-sec:has(> dd.bf-prof-fold) > dd.bf-prof-fold {
  grid-area: 1 / 1;
}

.bf-prof-sec:has(> dd.bf-prof-fold) > dt {
  position: relative;
  z-index: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-height: 1.875rem;
  /* Deja sitio al botón: el rótulo no se mete debajo de él. */
  max-width: calc(100% - 10rem);
  pointer-events: none;
}

.bf-prof-sec > dt {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--bf-surface-fg);
}

/* En el teléfono el rótulo con su pastilla ocupa dos líneas y lo desplegado no debe montarse sobre ella. */
@media (max-width: 619.98px) {
  .bf-prof-sec:has(> dd.bf-prof-fold) .bf-prof-fold > details[open] > summary { margin-bottom: 2.75rem; }
}

.bf-prof-sec:has(> dd.bf-prof-fold) .bf-prof-fold > details > summary {
  display: inline-flex;
  align-items: center;
  min-height: var(--bf-btn-height); /* chore 90.719 */
  box-sizing: border-box;
}

.bf-prof-mfa-body {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  text-align: left;
}

.bf-prof-mfa-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.25rem 0.75rem;
  font-size: 0.9rem;
}

.bf-prof-mfa-head b {
  font-weight: 600;
}

.bf-prof-pwd .bf-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  text-align: left;
}

.bf-prof-pwd .bf-form > button {
  align-self: flex-end;
}

/* ── Mis sesiones: la pantalla (chore 90.071 · U-9; rehecha en la 90.625) ─────
   La 90.616 la vistió con una tarjeta por sesión y, con las cuarenta que junta
   quien entra a menudo desde el mismo navegador, salía un muro de tarjetas
   gemelas con cuarenta «Cerrar» en rojo. Lo que se viene a buscar es un APARATO
   que no se reconoce, así que la pantalla se ordena por aparato:
     · una línea de cifras arriba, sin baldosas;
     · ESTA sesión en una fila discreta, con su pastilla (M-2), sin halo;
     · un grupo plegable por aparato (details/summary) con su resumen —cuántas,
       cuándo, desde dónde, si alguna está recordada— y, dentro, una lista fina;
     · el cerrar de cada fila es un icono que se revela al apuntar o enfocar y
       sólo se tiñe de rojo bajo el puntero: el rojo no está en reposo.
   Mobile-first (MF-2): todo nace apilado; desde el corte medio (MF-3) la fila de
   una sesión se pone en línea. */
.bf-sessions {
  display: flex;
  flex-direction: column;
  gap: 14px;
  margin-top: 16px;
}

/* HASTA ABAJO DE LA VISTA (chore 90.641), con la receta de la 404 (90.610): flex y no
   resta. El cuerpo del shell ya es una columna que llena el alto; la caja de página
   se lleva lo que quede, la pantalla lo suyo y la ficha todo, con el pie al fondo.
   Con muchas sesiones la ficha crece y la página rueda como siempre. */
.bf-pagebox:has(> main.bf-sessions) {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.bf-pagebox > main.bf-sessions {
  flex: 1 1 auto;
}

.bf-sess-card {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.bf-sess-card > .bf-sess-body {
  flex: 1 1 auto;
}

.bf-sess-body {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  padding: 1rem;
}

@media (min-width: 620px) {
  .bf-sess-body {
    padding: 1.125rem 1.25rem 1.25rem;
  }
}

.bf-sess-body > .bf-isub {
  margin-top: 0.375rem;
  margin-bottom: -0.375rem;
}

/* ── Las cifras: una línea ──
   Valor delante y rótulo detrás, separados por filetes finos; envuelven solas a
   ancho de teléfono (flex-wrap) sin media query. */
.bf-sess-sum {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0;
  margin: 0;
}

.bf-sess-sum > div {
  display: flex;
  flex-direction: row-reverse;
  justify-content: flex-end;
  align-items: baseline;
  gap: 0.4375rem;
  padding: 0 1rem;
  border-left: 1px solid var(--bf-border);
}

.bf-sess-sum > div:first-child {
  padding-left: 0;
  border-left: 0;
}

.bf-sess-sum dt {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-sess-sum dd {
  margin: 0;
  font-size: var(--bf-text-lead);
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  color: var(--bf-surface-fg);
}

/* En el teléfono, rejilla de dos por dos (chore 90.691): al envolver, la tercera cifra arrancaba con su filete
   izquierdo colgando del borde y las columnas no casaban. Aquí cada cifra se apila (valor sobre rótulo) en su
   celda y sin filetes. */
/* Excepción a MF-2: max-width porque la fila con filetes es la pieza de siempre y la rejilla solo existe en el
   teléfono; escrita al revés habría que deshacer la fila desde 620. */
@media (max-width: 619.98px) {
  .bf-sess-sum {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
  }

  .bf-sess-sum > div {
    flex-direction: column-reverse;
    align-items: flex-start;
    gap: 0.0625rem;
    padding: 0.5rem 0.75rem;
    border-left: 0;
    border-radius: 0.75rem;
    background: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  }

  .bf-sess-sum > div:first-child { padding-left: 0.75rem; }
}

/* ── La baldosa del aparato ──
   NEUTRA: el tipo es dibujo, no estado. El LED de la esquina dice si se ha usado
   en la última hora, siempre con la hora escrita al lado (M-2). */
.bf-session-tile {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
  color: var(--bf-surface-fg);
  font-size: var(--bf-text-ui);
  font-weight: 600;
}

.bf-session-tile > svg {
  width: 1.125rem;
  height: 1.125rem;
}

.bf-session-tile > .lamp {
  position: absolute;
  right: -0.1875rem;
  bottom: -0.1875rem;
  width: 0.5625rem;
  height: 0.5625rem;
  border-radius: 50%;
  background: var(--bf-off);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-surface-fg) 4%, var(--bf-panel-bg));
}

.bf-session-tile > .lamp.live {
  background: var(--bf-success);
}

.bf-session-main {
  display: flex;
  flex-direction: column;
  gap: 0.1875rem;
  min-width: 0;
}

.bf-session-device {
  font-size: var(--bf-text-body);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.005em;
  color: var(--bf-surface-fg);
  overflow-wrap: anywhere;
}

.bf-session-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.125rem 0.875rem;
  min-width: 0;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-session-where,
.bf-session-meta > .k {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  min-width: 0;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-session-where > span {
  font-family: var(--bf-font-mono);
  letter-spacing: 0;
  overflow-wrap: anywhere;
}

.bf-session-where > svg,
.bf-session-meta > .k > svg,
.bf-session-kept > svg {
  flex: none;
  width: 0.8125rem;
  height: 0.8125rem;
  opacity: 0.8;
}

.bf-session-meta > .k > svg,
.bf-session-kept > svg {
  color: var(--bf-accent-text);
  opacity: 1;
}

/* La marca de RECORDADA (chore 90.105): llave del acento y texto en voz baja. */
.bf-session-kept {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  min-width: 0;
  font-size: var(--bf-text-mini);
  line-height: 1.4;
  color: var(--bf-muted);
}

.bf-session-kept > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.bf-session-kept b {
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-session-seen {
  font-size: var(--bf-text-label);
  font-variant-numeric: tabular-nums;
  color: var(--bf-surface-fg);
  white-space: nowrap;
}

/* ── ESTA sesión: una fila hundida, sin halo ── */
.bf-session-here {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 0.875rem;
  padding: 0.75rem 0.875rem;
  border-radius: var(--bf-radius-btn);
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-border);
  animation: bf-sess-in var(--bf-dur-side) var(--bf-ease-out) backwards;
}

.bf-session-here > .bf-session-main {
  flex: 1 1 12rem;
}

.bf-session-here .bf-session-tile > .lamp {
  box-shadow: 0 0 0 2px var(--bf-panel-bg);
}

.bf-session-here > .bf-session-act {
  flex: none;
}

/* ── Un grupo por aparato ──
   Una lista con filetes, no tarjetas: el ritmo lo dan los separadores. */
.bf-sess-groups {
  display: flex;
  flex-direction: column;
  border-top: 1px solid var(--bf-border);
}

.bf-sess-group {
  border-bottom: 1px solid var(--bf-border);
  animation: bf-sess-in var(--bf-dur-side) var(--bf-ease-out) backwards;
  animation-delay: calc(min(var(--i, 0), 10) * var(--bf-dur-fast) / 3);
}

.bf-sess-group > summary {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-areas:
    "tile main chev"
    "tile seen chev";
  align-items: center;
  gap: 0.25rem 0.875rem;
  padding: 0.8125rem 0.5rem;
  margin: 0 -0.5rem;
  border-radius: var(--bf-radius-btn);
  list-style: none;
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-sess-group > summary::-webkit-details-marker {
  display: none;
}

.bf-sess-group > summary:hover {
  background: var(--bf-hover);
}

.bf-sess-group > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-sess-group > summary > .bf-session-tile {
  grid-area: tile;
}

.bf-sess-group > summary > .bf-session-main {
  grid-area: main;
}

.bf-sess-group > summary > .bf-session-seen {
  grid-area: seen;
  color: var(--bf-muted);
}

.bf-sess-group > summary > .chev {
  grid-area: chev;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  color: var(--bf-dim);
  transition: transform var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-sess-group > summary > .chev > svg {
  width: 1rem;
  height: 1rem;
}

.bf-sess-group[open] > summary > .chev {
  transform: rotate(90deg);
}

.bf-session-meta > .n {
  font-weight: 600;
  color: var(--bf-surface-fg);
}

@media (min-width: 620px) {
  .bf-sess-group > summary {
    grid-template-columns: auto minmax(0, 1fr) auto auto;
    grid-template-areas: "tile main seen chev";
    padding: 0.875rem 0.625rem;
    margin: 0 -0.625rem;
  }

  .bf-sess-group > summary > .bf-session-seen {
    color: var(--bf-surface-fg);
  }
}

/* ── El grupo ABIERTO (chore 90.656) ──
   Era una caja gris con filas planas gemelas y dos mandos sueltos debajo. Ahora:
   la cabecera abierta se queda con un velo y su galón girado; el cuerpo se abre
   con altura + fundido (::details-content, sin JavaScript; donde no hay soporte,
   abre en seco como siempre); las sesiones cuelgan de una LÍNEA DE TIEMPO fina
   que une sus LED, sin caja; y «ver N más» y «cerrar estas N» forman el pie. */
.bf-sess-group[open] > summary {
  background: color-mix(in srgb, var(--bf-surface-fg) 3.5%, transparent);
}

.bf-sess-group[open] > summary > .chev {
  color: var(--bf-surface-fg);
}

@supports selector(::details-content) {
  .bf-sess-group {
    interpolate-size: allow-keywords;
  }

  .bf-sess-group::details-content {
    height: 0;
    opacity: 0;
    overflow: clip;
    transition:
      height var(--bf-dur-side) var(--bf-ease-out),
      opacity var(--bf-dur-side) var(--bf-ease-out),
      content-visibility var(--bf-dur-side) allow-discrete;
  }

  .bf-sess-group[open]::details-content {
    height: auto;
    opacity: 1;
  }
}

/* Movimiento reducido: el grupo abre en seco. Regla aparte: un selector que el
   navegador no entienda tumbaría la lista entera. */
@media (prefers-reduced-motion: reduce) {
  .bf-sess-group::details-content {
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-sess-group::details-content {
  transition: none;
}

/* El cuerpo: lista y pie en una rejilla; «ver N más» a la izquierda y «cerrar
   estas N» a la derecha, en el mismo renglón. Sangrado a la altura del nombre. */
.bf-sess-gbody {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
  padding: 0.25rem 0 0.875rem;
}

.bf-sess-gbody > .bf-session-list,
.bf-sess-more[open] {
  grid-column: 1 / -1;
}

.bf-sess-gbody > .bf-sess-gact {
  grid-column: 2;
}

@media (min-width: 620px) {
  .bf-sess-gbody {
    padding-left: 0.6875rem;
  }
}

/* La línea de tiempo: un filete vertical que pasa por el centro de los LED. El LED
   lleva un aro del color de la ficha para que la línea no lo atraviese. */
.bf-session-list {
  position: relative;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bf-sess-gbody .bf-session-list::before {
  content: "";
  position: absolute;
  top: 1.25rem;
  bottom: 1.25rem;
  left: calc(0.875rem + 0.25rem - 0.5px);
  width: 1px;
  background: var(--bf-border-strong);
}

/* Una sesión: la HORA manda; dirección e inicio en voz baja; la marca de recordada
   con su llave. Nace en dos renglones; desde el corte medio, en uno, con columnas
   fijas para que la hora y la dirección se lean en vertical como una tabla. */
.bf-sess-gbody .bf-session {
  position: relative;
  display: grid;
  grid-template-columns: auto auto minmax(0, 1fr) auto;
  grid-template-areas:
    "led seen  seen  act"
    ".   where start act"
    ".   kept  kept  act";
  align-items: center;
  gap: 0.125rem 0.75rem;
  min-height: 2.5rem;
  padding: 0.375rem 0.375rem 0.375rem 0.875rem;
  border-radius: var(--bf-radius-btn);
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-sess-gbody .bf-session:hover,
.bf-sess-gbody .bf-session:focus-within {
  background: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
}

.bf-sess-gbody .bf-session > .bf-led {
  grid-area: led;
  position: relative;
  z-index: 1;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--bf-surface-fg) 3%, var(--bf-panel-bg));
}

.bf-sess-gbody .bf-session > .bf-led.ok {
  box-shadow:
    0 0 0 3px color-mix(in srgb, var(--bf-surface-fg) 3%, var(--bf-panel-bg)),
    0 0 0 5px color-mix(in srgb, var(--bf-success) 20%, transparent);
}

.bf-sess-gbody .bf-session > .bf-session-seen {
  grid-area: seen;
  font-size: var(--bf-text-ui);
  font-weight: 600;
}

.bf-sess-gbody .bf-session > .bf-session-where {
  grid-area: where;
  color: var(--bf-dim);
}

.bf-sess-gbody .bf-session > .bf-session-start {
  grid-area: start;
  font-size: var(--bf-text-label);
  color: var(--bf-dim);
}

.bf-sess-gbody .bf-session > .bf-session-kept {
  grid-area: kept;
}

.bf-sess-gbody .bf-session > .bf-session-act {
  grid-area: act;
}

@media (min-width: 620px) {
  .bf-sess-gbody .bf-session {
    grid-template-columns: auto 7rem 8rem minmax(0, 1fr) auto;
    grid-template-areas: "led seen where start act" ". kept kept kept act";
    gap: 0.125rem 0.875rem;
  }
}

@media (min-width: 1100px) {
  .bf-sess-gbody .bf-session {
    grid-template-columns: auto 7rem 8rem 12rem minmax(0, 1fr) auto;
    grid-template-areas: "led seen where start kept act";
  }
}

/* El cerrar de cada fila: un icono que no se ve hasta que la fila se apunta o
   se enfoca. En táctil (sin puntero fino) no hay «apuntar», así que está siempre,
   tenue. El rojo sólo bajo el puntero o con el foco: es el aviso de lo que hará. */
.bf-sess-x {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-s);
  background: transparent;
  color: var(--bf-dim);
  cursor: pointer;
  opacity: 0;
  transform: translateX(4px);
  transition:
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    transform var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-sess-x > svg {
  width: 1rem;
  height: 1rem;
}

.bf-sess-gbody .bf-session:hover .bf-sess-x,
.bf-sess-gbody .bf-session:focus-within .bf-sess-x {
  opacity: 1;
  transform: none;
}

.bf-sess-x:hover,
.bf-sess-x:focus-visible {
  color: var(--bf-danger-text);
  background: color-mix(in srgb, var(--bf-danger) 12%, transparent);
}

.bf-sess-x:focus-visible {
  outline: 2px solid var(--bf-danger);
  outline-offset: 1px;
}

@media (hover: none) {
  .bf-sess-x {
    opacity: 0.7;
    transform: none;
  }
}

/* El pie del grupo: un filete fino encima y los dos mandos en el mismo renglón. */
.bf-sess-gbody > .bf-sess-more:not([open]),
.bf-sess-gbody > .bf-sess-gact {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--bf-border);
}

.bf-sess-gbody > .bf-session-list + .bf-sess-gact,
.bf-sess-gbody > .bf-sess-more[open] + .bf-sess-gact {
  grid-column: 1 / -1;
}

/* «Ver N más»: un enlace del acento con su galón; abierto, se va y la lista sigue. */
.bf-sess-more > summary {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  padding: 0.3125rem 0.625rem;
  border-radius: var(--bf-radius-s);
  list-style: none;
  font-size: var(--bf-text-label);
  font-weight: 600;
  color: var(--bf-accent-text);
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-sess-more > summary::after {
  content: "";
  width: 0.375rem;
  height: 0.375rem;
  margin-top: -0.1875rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
}

.bf-sess-more > summary::-webkit-details-marker {
  display: none;
}

.bf-sess-more > summary:hover {
  background: var(--bf-hover);
}

.bf-sess-more > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
}

.bf-sess-more[open] > summary {
  display: none;
}

/* La continuación cuelga de la misma línea de tiempo. */
.bf-sess-more > .bf-session-list {
  margin-top: 0;
}

/* «Cerrar estas N», a la derecha del pie: callado hasta que se apunta. */
.bf-sess-gact {
  display: flex;
  justify-content: flex-end;
}

.bf-sess-gact > .bf-btn:hover:not(:disabled),
.bf-sess-gact > .bf-btn:focus-visible {
  color: var(--bf-danger-text);
  background: color-mix(in srgb, var(--bf-danger) 10%, transparent);
}

/* Sólo está la actual: la respuesta buena, dicha en voz baja con el visto.
   Centrada, como todo vacío (chore 90.682). */
.bf-session-alone {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: 0.625rem;
  margin: 0;
  padding: 0.875rem 1rem;
  border-radius: var(--bf-radius-btn);
  border: 1px dashed var(--bf-border-strong);
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
}

.bf-session-alone > svg {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--bf-success-text);
}

/* El pie lleva su «i» delante, a la altura de la primera línea (M-10). */
.bf-sess-card > .bf-foot {
  flex-wrap: nowrap;
  align-items: flex-start;
  gap: 0.5rem;
  line-height: 1.5;
}

.bf-sess-card > .bf-foot > svg {
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  margin-top: 0.1875rem;
}

@keyframes bf-sess-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bf-session-here,
  .bf-sess-group,
  .bf-sess-group > summary > .chev,
  .bf-sess-x {
    animation: none;
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-session-here,
:root[data-reduce~="motion"] .bf-sess-group,
:root[data-reduce~="motion"] .bf-sess-group > summary > .chev,
:root[data-reduce~="motion"] .bf-sess-x {
  animation: none;
  transition: none;
}

/* Zona de administración del portal (12.004, contrato 00.006 §3.4). */
.admin {
  margin-top: 2rem;
  padding-top: 1rem;
  border-top: 1px solid var(--bf-border);
}

.admin ul {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.admin a {
  display: inline-flex;
  align-items: center;
  padding: 0 11px;
  height: 30px;
  border: 1px solid var(--bf-field-line);
  border-radius: var(--bf-radius-m);
  background: var(--bf-surface-bg);
  color: var(--bf-surface-fg);
  font-size: var(--bf-button-font-size-small);
  text-decoration: none;
}

.admin a:hover {
  background: var(--bf-panel-bg);
  text-decoration: none;
}

/* ── Disposición de la administración ──────────────────────────────────────────
   Sólo contenido: la navegación es la del shell y ÚNICAMENTE la del shell (chore
   90.061 · decisión P-5). Hasta entonces esta zona era «lateral + contenido», con
   un `.bf-nav` propio que duplicaba el menú y una regla que lo escondía en cuanto
   el shell hidrataba; el estudio lo retiró junto con su CSS. El marco de la página
   (relleno, ancho y separación) lo pone ya el cuerpo del shell (`.bf-body` de la
   maqueta), así que aquí no se repite. */

.bf-admin {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* min-width:0 es lo que permite que un hijo ancho (una tabla) se ENCOJA dentro
   del flex en vez de estirar la columna. */
.bf-admin-main {
  flex: 1 1 auto;
  min-width: 0;
  max-width: 100%;
}

/* SIN TARJETA PADRE (chore 90.420). El `.bf-panel` que envuelve la vista pintaba un
   marco —fondo, borde, radio y relleno— alrededor del módulo ENTERO: la cabecera con
   sus acciones, los filtros, el contenido y la leyenda iban dentro de una caja grande,
   y las tarjetas del propio contenido (la de la tabla, las de la ficha, las de las
   instancias) salían como cajas dentro de otra caja. Como en Hugin y en Latanur, y
   como ya iban el perfil, los ajustes, las sesiones y los avisos, la vista va
   directamente sobre la mesa: el margen de página es el del cuerpo del shell, y las
   tarjetas que quedan son las del contenido. Se quita AQUÍ, en el único contenedor
   de las vistas de administración (_AdminLayout), no vista a vista.

   El panel sigue siendo el marco que desborda a lo ancho (R-4, más abajo), y un
   contenedor con desbordamiento recorta lo que se pinta fuera de sus hijos: sin
   relleno, el anillo de foco de «Nuevo usuario», pegado al canto, salía cortado. Por
   eso guarda una franja (--bf-view-bleed: los 2 px del trazo del anillo más sus 2 de
   separación) que devuelve con un margen negativo, así que el contenido se queda
   EXACTAMENTE en el margen de página. Sin `max-width: 100%`: con el margen negativo
   recortaría la caja por la derecha lo que el margen le da por la izquierda. */
.bf-admin-main > .bf-panel {
  --bf-view-bleed: 0.25rem;
  overflow-x: auto;
  margin: calc(-1 * var(--bf-view-bleed));
  padding: var(--bf-view-bleed);
  border: 0;
  border-radius: 0;
  background: none;
}

/* La tabla mide lo que mide su contenido y desborda DENTRO del panel; con
   `min-width: 100%` sigue llenándolo cuando le sobra sitio. Esto es lo que hace
   que la PRIMERA COLUMNA siga siendo legible a 360 px: nadie la reparte a partes
   iguales ni la recorta con puntos suspensivos, es el ancho de lo que dice. */
.bf-admin-main .bf-table {
  min-width: 100%;
  width: max-content;
}

/* ── RASTRO DE MIGAS (chore 90.272 · T8) ─────────────────────────
   Con la cabecera sin título grande, el sitio donde se está lo dice el PRODUCTO
   en un <nav slot="crumbs"> (contrato 00.004 v1.7.0 §3.3). Quien lo pinta casi
   siempre es el shell, que lo lee y lo re-viste en su cabecera con sus iconos y
   sus chevrones; mientras tanto esconde el proyectado con
   `slot[name="crumbs"] { display: none }`.

   Esto de aquí es para el otro caso, el mismo de la reserva: sin JavaScript no
   hay Shadow DOM, nadie lo esconde y el nav se lee en el light DOM. Sin una
   regla propia saldría como lista con viñetas en vertical encima del título. No
   se le pone regla de ocultación: la del shell ya lo tapa cuando hidrata, y
   añadir otra aquí dejaría el rastro en manos de dos sitios.

   Se lee como lo que es —una pista, no un título—: cuerpo de leyenda y tinta
   atenuada, con la última miga en la tinta de la superficie y con peso, que es
   el mismo reparto que hace el shell. Los separadores van por CSS a propósito:
   escritos en el marcado se los llevaría el lector de pantalla. */
.bf-crumbs ol {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  margin: 0 0 0.75rem;
  padding: 0;
  list-style: none;
  color: var(--bf-dim);
  font-size: var(--bf-legend-font-size);
}

.bf-crumbs li:not(:last-child)::after {
  content: "›";
  margin-left: 0.375rem;
  opacity: 0.55;
}

.bf-crumbs a {
  color: inherit;
  border-radius: var(--bf-radius-s);
}

.bf-crumbs a:hover,
.bf-crumbs a:focus-visible {
  color: var(--bf-surface-fg);
}

/* Dónde se está: no es un enlace y no compite con el h1, pero tampoco se
   confunde con el camino que lleva hasta él. */
.bf-crumbs [aria-current="page"] {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

/* ── Navegación de RESERVA (chore 90.084) ─────────────────────────────────────
   La contrapartida de la regla de arriba. Cuando el shell NO hidrata —sin
   JavaScript, con el contexto caído (data-context=degraded), con la sesión
   obsoleta (stale), con la sesión revocada (revoked, chore 90.142), sin sesión
   ninguna aquí (anonymous, chore 90.221) o si el bundle
   no llega—, el lateral no existe y la página se
   quedaba sin ningún camino a otra sección. `_AdminLayout` pinta entonces esta
   lista al FINAL del contenido: no es un segundo lateral —no compite por el
   sitio del shell ni repite su forma—, es el pie del que se tira cuando no hay
   menú.

   La visibilidad va AL REVÉS de lo habitual y es deliberado: se ve por defecto y
   se esconde cuando el shell dice que ya tiene la suya. Así el caso sin
   JavaScript —donde ninguna regla se puede activar desde el cliente— es el que
   sale bien solo (progressive enhancement, contrato 00.004 §7.5).

   RE-VESTIDA en la chore 90.261: es una tarjeta y se viste como ellas (S-3 ·
   90.257), con el velo y dibujada por su canto; los enlaces son filas de menú con el
   realce en la superficie (S-5), y la sección actual se marca como el segmento
   puesto (M-9). La VISIBILIDAD no se toca: sigue siendo la regla del final. */
.bf-reserve {
  margin-top: 1.125rem;
  padding: var(--bf-panel-padding);
  border: 1px solid var(--bf-border);
  border-radius: var(--bf-panel-radius);
  background-color: var(--bf-card-veil);
  color: var(--bf-surface-fg);
  font-size: var(--bf-panel-font-size);
}

.bf-reserve-lead {
  color: var(--bf-dim);
  font-size: var(--bf-legend-font-size);
}

/* Rótulo de módulo: es una separación, no un título de pantalla — de ahí el
   cuerpo de leyenda y el color atenuado, para que no compita con el h1 de la
   página que tiene encima. */
.bf-reserve h2 {
  margin-top: 0.875rem;
  font-size: var(--bf-legend-font-size);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

/* En fila y con envoltura: al pie de la página, una lista en columna sería una
   tira larguísima de un solo enlace por renglón. A 360 px el wrap la apila sola.
   Cada enlace lleva su propio relleno (el área que se apunta), así que el hueco
   entre ellos baja en la misma medida —entre dos rótulos siguen quedando 18 px— y
   la lista retrocede medio relleno para que el primero siga alineado con el rótulo
   de módulo de encima. */
.bf-reserve ul {
  list-style: none;
  margin: 0.375rem -0.5rem 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.125rem;
}

.bf-reserve a {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: var(--bf-radius-input);
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-reserve a:hover {
  background-color: var(--bf-hover);
}

/* «Aquí estás»: el TINTE DEL ACENTO, que es lo que el acento dice ahora (M-1
   reescrito), con la tinta del texto y no la de enlace —no es un sitio al que ir—
   y el peso. No el velo de seleccionado del segmentado: ése se lee porque va sobre
   un carril gris, y aquí, sobre la tarjeta, en claro era blanco sobre blanco y la
   actual se quedaba en la misma tinta que los enlaces, distinguida sólo por la
   negrita. El apuntado no la cambia. */
.bf-reserve a[aria-current="page"] {
  color: var(--bf-surface-fg);
  font-weight: 600;
  background-color: color-mix(in srgb, var(--bf-accent) 14%, transparent);
}

/* La regla que impide el DOBLE menú (decisión P-5, chore 90.061): en cuanto el
   shell aporta su navegación, la reserva desaparece. `loading` entra también
   para que no parpadee entre la hidratación y la llegada del contexto —ahí el
   menú del shell ya está en camino y esta lista sobra. */
bifrost-shell[data-context="loading"] .bf-reserve,
bifrost-shell[data-context="ready"] .bf-reserve {
  display: none;
}

/* ── Esqueleto de página-tabla de administración (R-4 · chore 90.100) ──────────
   Las 13 vistas CRUD servidas emiten LO MISMO dentro del mismo `.bf-panel`:
   título, tabla ancha y, algunas, una ficha de alta/edición. Por eso su esqueleto
   responsive se escribe UNA VEZ aquí y no vista a vista (unidad R-4 del estudio
   mobile-first, convenio MF-5). Nivel objetivo: USABLE a 360 px —consultar y
   hacer acciones simples—, no paridad de escritorio; lo que la maqueta convierta
   en tarjetas, matriz o árbol llega con la chore de traspaso de esa vista.

   1 · El scroll horizontal es de la TABLA, aunque el marco sea el panel.
   El desbordamiento ya estaba resuelto (el panel es el marco con scroll), pero
   arrastraba consigo todo lo que el panel contiene: al empujar la tabla a la
   derecha, el título y la ficha se iban de la pantalla y dejaban un vacío al
   lado de la tabla. Clavándolos al filo, el arrastre parece de la tabla, que es
   de quien el usuario cree que es.

   `left` vale el relleno del panel y no 0 a propósito: sin desplazamiento la
   pieza YA está a esa distancia del filo, así que no hay salto al empezar a
   arrastrar. Desde la chore 90.420 ese relleno ya no es el de un marco, sólo la
   franja que salva el anillo de foco (--bf-view-bleed). Y la lista es explícita
   —no un `*`— para no convertir en contexto de apilamiento a los contenedores
   de superposición de las pantallas de actividad, que también cuelgan del panel. */
.bf-admin-main > .bf-panel > h1,
.bf-admin-main > .bf-panel > h2,
.bf-admin-main > .bf-panel > h3,
.bf-admin-main > .bf-panel > p,
.bf-admin-main > .bf-panel > form,
.bf-admin-main > .bf-panel > .bf-page-header,
.bf-admin-main > .bf-panel > .bf-toolbar {
  position: sticky;
  left: var(--bf-view-bleed);
}

/* 2 · Barra de acciones y filtros: envolver no basta si una pieza es más ancha
   que la pantalla. `.bf-toolbar` y `.bf-page-header` ya son flex-wrap, pero un
   <select> con una opción larga no se encoge por defecto (min-width:auto) y se
   lleva por delante el ancho de la fila. */
.bf-toolbar > *,
.bf-page-header > * {
  min-width: 0;
  max-width: 100%;
}

/* 3 · La ficha de alta y edición, a UNA COLUMNA bajo 620 (MF-3).
   Las vistas emiten `<p><label>Texto <control></label></p>`: en escritorio el
   control va detrás del texto y mide lo que el navegador quiera —36, 170 y 248 px
   en la misma ficha—, y a 360 px eso deja campos de dos centímetros junto a su
   etiqueta. Aquí el control baja a su línea y ocupa el ancho de la ficha; desde
   620 vuelve al renglón, que es como se ve hoy en escritorio.

   Las casillas se quedan siempre en línea: una casilla al final de su frase es lo
   correcto en cualquier ancho, y estirarla no la haría más fácil de marcar. */
.bf-form p > label {
  display: block;
}

.bf-form p > label > input:not([type="checkbox"]):not([type="radio"]),
.bf-form p > label > select,
.bf-form p > label > textarea {
  display: block;
  width: 100%;
  max-width: 100%;
  margin-top: 6px;
}

/* El selector de fichero (la imagen de perfil, 02.005) no cabe en un teléfono y
   no encoge solo: su ancho MÍNIMO es el del botón nativo más el hueco del nombre
   del fichero —unos 306 px en Chromium—, y no lo alcanza la regla de arriba
   porque en esa ficha el control es HERMANO de su etiqueta, no hijo. Sin este
   tope, el control desborda su panel y con él la página (MF-5). */
.bf-form input[type="file"] {
  max-width: 100%;
}

@media (min-width: 620px) {
  .bf-form p > label {
    display: inline;
  }

  .bf-form p > label > input:not([type="checkbox"]):not([type="radio"]),
  .bf-form p > label > select,
  .bf-form p > label > textarea {
    display: inline-block;
    width: auto;
    margin-top: 0;
  }
}

/* ═══ 13 · Táctil (MF-4 · docs/archivo/estudio-mobile-first-bifrost3.md) ══════════════
   Bajo pointer grueso todo objetivo interactivo del catálogo alcanza
   --bf-tap-size (44 px) de área efectiva con --bf-tap-gap (8 px) de separación.
   La métrica visual de ESCRITORIO (botón 34, campo 36) no se toca: este bloque
   solo existe en dispositivos táctiles, que es otra dimensión que el tema (el
   convenio M-7 de la maqueta habla del tema, no del medio de entrada). Se usa
   min-height y no height para que las tallas grandes (.lg, ya de 42-44) y los
   contenidos de varias líneas sigan mandando. */
@media (pointer: coarse) {
  /* Botones, en todas sus tallas. */
  .bf-btn,
  .bf-btn.sm,
  .bf-idp,
  .bf-filter {
    min-height: var(--bf-tap-size);
  }

  /* El buscador: la caja crece y el campo de dentro la llena (align-self). */
  .bf-field-search {
    min-height: var(--bf-tap-size);
    height: auto;
  }

  .bf-field-search[data-filled="true"] > button {
    width: 2rem;
    height: 2rem;
  }

  /* Campos: el campo sube a 44. El selector repite los tipos de la base porque
     aquélla fija height con más especificidad que .bf-input. */
  .bf-input,
  input.bf-input,
  select.bf-input,
  .bf-field input:not([type="checkbox"]):not([type="radio"]),
  .bf-field select {
    min-height: var(--bf-tap-size);
    height: auto;
  }

  /* Ítems de menú y de lista: pestañas, paginación, segmentado, preferencias
     y la fila-botón de los hitos del forense. */
  .bf-tabs a,
  .bf-seg i,
  .bf-seg button,
  .bf-seg a,
  .bf-seg label,
  .bf-iconpick label,
  .bf-hit,
  .bf-tree-group > summary {
    min-height: var(--bf-tap-size);
  }

  .bf-seg i,
  .bf-seg button,
  .bf-seg a {
    display: inline-flex;
    align-items: center;
  }

  /* El paginador de cinco mandos (chore 90.259): crecen las cuatro flechas y el
     campo de salto; el rótulo y el total son texto, no objetivo. */
  .bf-page {
    gap: var(--bf-tap-gap);
  }

  .bf-page > a,
  .bf-page > span {
    min-width: var(--bf-tap-size);
    min-height: var(--bf-tap-size);
  }

  .bf-page .jump input {
    min-height: var(--bf-tap-size);
  }

  /* Casillas e interruptores: crece el objetivo (la etiqueta entera es
     pulsable), no el dibujo, que es métrica del sistema. */
  .bf-check {
    min-height: var(--bf-tap-size);
  }

  /* Piezas de gestión (chore 90.117). Mismo criterio de siempre: crece el ÁREA,
     no el dibujo. */

  /* Resultado del buscador de personas: la fila entera se pulsa. */
  .bf-pick .opt {
    min-height: var(--bf-tap-size);
  }

  /* Casilla de selección de una tabla: el dibujo se queda en 17 px y el objetivo
     lo da la celda. 13 + 17 + 13 ≈ 44 con el filete, y como la fila mide lo que
     su celda más alta, sube la fila entera —que es lo que se acierta con el
     pulgar—. El relleno lateral iguala el margen de error a lo ancho. */
  .bf-table th.ck,
  .bf-table td.ck {
    width: var(--bf-tap-size);
    padding: 13px 0 13px 13px;
  }

  /* Dos acciones seguidas en la misma fila se aciertan mal con el pulgar. */
  .bf-table td.acts > form,
  .bf-table td.acts > a,
  .bf-table td.acts > button {
    margin-left: var(--bf-tap-gap);
  }

  /* Modo reordenar: en un teléfono NO hay arrastre que valga, así que subir y
     bajar son la única forma de mover un nodo — y tienen que acertarse. La manija
     crece también: aunque no arrastre, es lo que dice de dónde se agarra. */
  .bf-node .updown i,
  .bf-node .updown button,
  .bf-node .grip {
    min-width: var(--bf-tap-size);
    min-height: var(--bf-tap-size);
  }

  .bf-node .grip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }

  .bf-node .updown {
    gap: var(--bf-tap-gap);
  }

  /* Casillas del código: el hueco baja al mínimo del convenio para que las seis
     cajas se queden con todo el ancho que sobra. Su alto (54) ya pasa de 44 y en el
     tramo medio (620-900) el ancho también llega. Por debajo de 620 esta forma ni
     se pinta: allí la entrada es un solo campo a ancho completo, porque seis
     objetivos de 44 con sus huecos piden 304 px libres y a 360 no los hay (chore
     90.122, con la aritmética junto a `.bf-code-entry`). */
  .bf-code {
    gap: var(--bf-tap-gap);
  }

  /* Enlaces de ACCIÓN del circuito de acceso (R-2 · chore 90.098): «he olvidado
     mi contraseña», «volver al acceso», «pedir otro enlace». Son la SALIDA de una
     pantalla que hoy se termina en el teléfono —el enlace de recuperación se abre
     ahí—, y como texto suelto miden lo que su renglón: ~17 px. No los alcanzaba
     ninguna regla de arriba, que hablan de botones y de campos. Crece el objetivo;
     el texto no cambia de tamaño ni de color (eso es métrica de la maqueta). El
     relleno lateral da el mismo margen de error a lo ancho que a lo alto. */
  .bf-authlink a {
    display: inline-flex;
    align-items: center;
    min-height: var(--bf-tap-size);
    padding: 0 var(--bf-tap-gap);
  }

  /* Accesos de la zona de administración del portal (R-3 · chore 90.099). Se
     dibujan como botones pequeños pero NO son `.bf-btn`: llevan su propio
     `height: 30px`, que aquí hay que soltar (`height: auto`) para que el mínimo
     mande. La separación de la lista sube al mínimo del convenio: seis accesos
     seguidos a 0,6rem se aciertan mal con el pulgar. */
  .admin a {
    min-height: var(--bf-tap-size);
    height: auto;
  }

  .admin ul {
    gap: var(--bf-tap-gap);
  }

  /* Fila interactiva de tabla: la altura la dan los rellenos de celda. */
  .bf-table th,
  .bf-table td {
    padding-top: 13px;
    padding-bottom: 13px;
  }

  /* Ficha heredada de administración (R-4 · chore 90.100). Sus controles son
     nativos y sin vestir —18 a 21 px de alto medidos a 360 px—, así que no los
     alcanzaba ninguna de las reglas de arriba, que hablan de `.bf-input` y de
     `.bf-field`. Vestirlos es trabajo de la chore de traspaso de cada vista;
     que se puedan acertar con el dedo, no: eso es MF-4 y es de aquí.

     El selector mira a la ficha ENTERA y no sólo al patrón `p > label > control`
     (chore 90.099): «Tu perfil» escribe la etiqueta y su control como HERMANOS
     —`<label for>` + `<select>`—, y así se quedaba con dos desplegables de 18 px
     y un selector de fichero de 20 en un teléfono. La forma del marcado no puede
     decidir si un control se puede pulsar. */
  .bf-form input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]),
  .bf-form select,
  .bf-form textarea,
  .bf-form button {
    min-height: var(--bf-tap-size);
  }

  /* La casilla sigue midiendo lo que dibuja; el objetivo es su etiqueta entera,
     pulsable de punta a punta. Se le devuelve la caja de bloque —desde 620 la
     ficha la deja en línea, y una caja en línea ignora min-height— sin tocar la
     colocación: el control de dentro sigue en el mismo renglón que su texto. */
  .bf-form p > label {
    display: block;
    min-height: var(--bf-tap-size);
  }

  .bf-form p > button + button {
    margin-left: var(--bf-tap-gap);
  }

  /* El calendario (chore 90.260). Cada DÍA es un objetivo de 44 px: la casilla
     entera se pulsa, y la cara redonda de dentro baja lo que mide la separación,
     así que entre dos caras quedan los 8 px del convenio. Pegadas las ÁREAS y no
     las caras, a propósito: siete objetivos de 44 con 8 px entre ellos piden
     356 px, y a 360 no caben con los márgenes; siete de 44 juntos son 308 y sí.
     Los niveles de meses y de años siguen midiendo lo mismo que el de días (base,
     §5), ahora con la tira de iniciales de 1,5 rem. Crecen también el botón del
     campo, las dos flechas, el título y «Hoy». */
  .bf-date-open {
    right: 0;
    width: var(--bf-tap-size);
    height: var(--bf-tap-size);
  }

  .bf-date[data-dp="on"] > input[type="date"] {
    padding-right: var(--bf-tap-size);
  }

  .bf-dp-nav {
    width: var(--bf-tap-size);
    height: var(--bf-tap-size);
  }

  .bf-dp-title,
  .bf-dp-today {
    height: auto;
    min-height: var(--bf-tap-size);
  }

  .bf-dp-grid th,
  .bf-dp-grid td {
    width: var(--bf-tap-size);
  }

  .bf-dp-grid th {
    height: 1.5rem;
  }

  .bf-dp-grid td {
    height: var(--bf-tap-size);
  }

  .bf-dp-grid td > span {
    width: calc(var(--bf-tap-size) - var(--bf-tap-gap));
    height: calc(var(--bf-tap-size) - var(--bf-tap-gap));
  }

  .bf-dp[data-level="month"] .bf-dp-grid td,
  .bf-dp[data-level="year"] .bf-dp-grid td {
    width: calc(var(--bf-tap-size) * 7 / 3);
    height: calc((1.5rem + 6 * var(--bf-tap-size)) / 4);
  }

  .bf-dp[data-level="month"] .bf-dp-grid td > span,
  .bf-dp[data-level="year"] .bf-dp-grid td > span {
    width: calc(var(--bf-tap-size) * 7 / 3 - var(--bf-tap-gap));
    height: calc((1.5rem + 6 * var(--bf-tap-size)) / 4 - var(--bf-tap-gap));
  }
}

/* EL SEGMENTADO DEL CENTRO DE CONTROL en Ajustes (chore 90.363; calcado del `.cc-seg` del shell en la 90.503): la
   misma cápsula de píldoras —alto 1,35 rem, letra de 10 px en versalita, glifo de 0,85 rem—, el velo de seleccionado
   del shell con la tinta del acento, que LLEGA una vez (su `bf-select-in`), y sin sombra. El carril es el velo de
   campo de la página: el del shell (blanco al 66 %) está pensado para su cristal y sobre una tarjeta no se vería.
   Sigue siendo un grupo de radios. */
.bf-seg.bf-seg-cc {
  gap: 0.125rem;
  padding: 0.125rem;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  --seg-cc-sel: color-mix(in srgb, var(--bf-relief-light) 70%, transparent);
}

:root[data-theme="dark"] .bf-seg.bf-seg-cc {
  background: color-mix(in srgb, var(--bf-tint-cool) 7%, transparent);
  --seg-cc-sel: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-seg.bf-seg-cc {
    background: color-mix(in srgb, var(--bf-tint-cool) 7%, transparent);
    --seg-cc-sel: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
  }
}

.bf-seg.bf-seg-cc label {
  justify-content: center;
  height: 1.35rem;
  min-width: 1.85rem;
  padding: 0 0.55rem;
  border: 0;
  border-radius: 999px;
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  white-space: nowrap;
  transition: background-color 0.2s var(--bf-ease-out), color 0.2s var(--bf-ease-out);
}

.bf-seg.bf-seg-cc label svg { width: 0.85rem; height: 0.85rem; flex: none; }

.bf-seg.bf-seg-cc label:hover:not(:has(input:checked)) {
  background: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-seg.bf-seg-cc label:has(input:checked) {
  background: var(--seg-cc-sel);
  box-shadow: none;
  color: var(--bf-accent);
  font-weight: 700;
  animation: bf-set-select-in 0.2s var(--bf-ease-out);
}

/* Deshabilitado (el modo edición mientras el shell no dice que sabe hacerlo): se ve, pero no invita. */
.bf-seg.bf-seg-cc label:has(input:disabled) { opacity: 0.5; cursor: not-allowed; }
.bf-seg.bf-seg-cc label:has(input:disabled):hover { background: transparent; color: var(--bf-muted); }

/* ── AJUSTES (chore 90.380) — la pantalla de Hugin: índice de secciones a la izquierda y UNA sección a la vez a
   la derecha. Por debajo del corte el índice se tumba en fila encima de la sección (MF-1). Las secciones reusan el
   panel del perfil (.bf-prof-panel) y sus renglones; aquí sólo va lo que es de esta pantalla. */
.bf-set { display: flex; flex-direction: column; gap: 1rem; padding-bottom: 2rem; }
/* Chore 90.502: la pista va dentro de la cabecera (su `> p`), a 0,25 rem del título como la `.page-hint` de Hugin; la
   cabecera no suma su margen al hueco de la pantalla, que ya es el 1 rem de la `.page-head` de allí. */
.bf-set > .bf-page-header { margin-bottom: 0; }
.bf-set-split { display: flex; flex-direction: column; gap: 1rem; }
.bf-set-side { display: flex; flex-wrap: wrap; gap: 0.25rem; }
/* EL ÍNDICE, IDÉNTICO al de Ajustes de Hugin (`.folder-link`, calendar.css; chore 90.502): gris en reposo, velo de
   paso al apuntar, el VELO DE SELECCIONADO con la tinta del texto y el glifo en naranja en la puesta —que LLEGA una vez,
   su `select-in`—, y el aro del foco de Hugin, que entra cerrándose y sin el filete de la casa. Por debajo del corte
   conserva la altura de dedo (MF-1). */
.bf-set-link {
  display: flex; align-items: center; gap: 0.5rem; min-height: var(--bf-tap-size, 2.5rem);
  padding: 0.375rem 0.625rem; border-radius: var(--bf-radius-btn, 0.5rem);
  color: var(--bf-muted); text-decoration: none; font-size: var(--bf-text-ui, 0.8125rem); text-align: left;
  transition: background-color 0.15s ease, color 0.15s ease;
}
.bf-set-link svg { width: 1rem; height: 1rem; flex: none; }
.bf-set-link > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bf-set-link:hover { background-color: var(--bf-hover); color: var(--bf-surface-fg); }
.bf-set-link[aria-current="page"] {
  background-color: var(--bf-selected); color: var(--bf-surface-fg); font-weight: 600;
  animation: bf-set-select-in var(--bf-dur-base) var(--bf-ease-out);
}
.bf-set-link[aria-current="page"] svg { color: var(--bf-accent); }
.bf-set-link:focus-visible {
  outline: 2px solid var(--bf-accent); outline-offset: 2px; box-shadow: none;
  animation: bf-set-focus-in var(--bf-dur-base) var(--bf-ease-out);
}
@keyframes bf-set-select-in { from { background-color: transparent; } }
@keyframes bf-set-focus-in { from { outline-color: transparent; outline-offset: 7px; } }
.bf-set-body { min-width: 0; flex: 1; display: flex; flex-direction: column; gap: 1rem; }
/* Cada sección, pintada siempre y enseñada una a una (chore 90.502): por dentro, el mismo reparto que tenía el cuerpo. */
.bf-set-sec { display: flex; flex-direction: column; gap: 1rem; }
/* La sección LLEGA al cambiar de apartado (chore 90.561), calcado de las fichas de Ajustes de Hugin (manage.css,
   `#settings-form > [data-group]:not([hidden]) > *`): su `item-in` de 460 ms, escalonado de 60 en 60 ms hasta el cuarto.
   Las secciones se enseñan y esconden con `hidden`, así que el paso de `display: none` a flex vuelve a disparar la
   animación sin tocar settings.ts: vale igual para el clic en el índice que para atrás y adelante. */
.bf-set-sec:not([hidden]) > * { animation: bf-item-in 460ms cubic-bezier(0.16, 1, 0.3, 1) both; }
.bf-set-sec:not([hidden]) > :nth-child(2) { animation-delay: 60ms; }
.bf-set-sec:not([hidden]) > :nth-child(3) { animation-delay: 120ms; }
.bf-set-sec:not([hidden]) > :nth-child(n + 4) { animation-delay: 180ms; }
@media (prefers-reduced-motion: reduce) {
  .bf-set-sec:not([hidden]) > * { animation: none; }
}
:root[data-reduce~="motion"] .bf-set-sec:not([hidden]) > * { animation: none; }
/* Y dentro de cada tarjeta, la pista a 0,125 rem de su título, como la de las tarjetas de Ajustes de Hugin (su `mt-0.5`
   bajo el `h2`). Sólo aquí: el perfil conserva su aire. */
.bf-set-sec .bf-prof-panel-head { margin-bottom: 0.125rem; }
/* TODOS los interruptores de Ajustes al canto derecho de su renglón (chore 90.503), en cualquier ancho: el rótulo y su
   pista ocupan la izquierda y el interruptor se va al final, como el mando de los renglones con segmentado. Sólo cambia
   el orden visual: la casilla sigue siendo la primera de la etiqueta, y el teclado y el formulario no lo notan. */
.bf-set-sec .bf-srow > .bf-check { flex: 1 1 auto; min-width: 0; gap: 0.875rem; }
.bf-set-sec .bf-srow > .bf-check > span { flex: 1 1 auto; min-width: 0; }
.bf-set-sec .bf-srow > .bf-check > input.bf-switch { order: 2; margin-left: auto; }
/* La página inicial (chore 90.951): el desplegable a la derecha, como el segmentado del idioma y el tema, con ancho para
   el nombre de un módulo; en el teléfono, cuando el renglón envuelve, ocupa la línea entera. */
.bf-set-sec .bf-srow select.bf-set-start { width: 16rem; max-width: 100%; }
/* Excepción a MF-2: max-width porque solo el teléfono estira el desplegable; el ancho fijo es el de los demás
   mandos del renglón. */
@media (max-width: 619.98px) {
  .bf-set-sec .bf-srow > .aside:has(> select.bf-set-start) { flex: 1 1 100%; }
  .bf-set-sec .bf-srow select.bf-set-start { width: 100%; }
}
.bf-set-fields { display: grid; gap: 1rem; padding-top: 0.5rem; }
.bf-set-fields .bf-field { display: flex; flex-direction: column; gap: 0.35rem; max-width: 34rem; }
/* Descartar y guardar ya no van al pie de cada sección: suben a la cabecera de la pantalla (chore 90.504), en su
   `.acts`, a la derecha del título como las `.page-actions` de Hugin; en el teléfono envuelven bajo él. */
.bf-set-note { margin: 1rem 0 0; color: var(--bf-muted); font-size: var(--bf-text-mini, 0.75rem); }
@media (min-width: 900px) {
  .bf-set-split { flex-direction: row; align-items: flex-start; }
  .bf-set-side { flex-direction: column; flex-wrap: nowrap; width: 14rem; flex: none; position: sticky; top: 1rem; }
  .bf-set-link { min-height: 0; }
}

/* ── AJUSTES · la configuración de la instalación (chore 90.398) — la parte «Instalación» del índice lleva su rótulo
   de grupo encima de sus secciones (Apariencia, Correo, Retención de datos); los campos cortos van en rejilla de
   dos columnas cuando caben (patrón intrínseco, sin media query) y la prueba del correo es una fila propia antes
   del par Descartar/Guardar. */
.bf-set-group {
  margin: 0.75rem 0 0.25rem; padding: 0 0.75rem;
  color: var(--bf-muted); font-size: var(--bf-text-mini, 0.75rem); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
}
.bf-set-side > .bf-set-group { flex-basis: 100%; }
/* Por debajo del corte ancho (chore 90.691), el índice es UNA tira que se desliza: envuelto, «Personal» ocupaba un
   renglón, el rótulo «Instalación» otro y sus tres secciones un tercero, y la pantalla abría con 170 px de índice.
   El rótulo de grupo se aparta de la vista pero sigue ahí para el lector, y la tira lleva el carril de las pestañas. */
@media (max-width: 899.98px) {
  .bf-set-side {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    padding: 0.1875rem; border-radius: calc(var(--bf-radius-btn, 0.5rem) + 0.1875rem);
    background: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  }
  .bf-set-side::-webkit-scrollbar { display: none; }
  .bf-set-side > .bf-set-link { flex: none; }
  .bf-set-side > .bf-set-group {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }
}
.bf-set-grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.bf-set-grid .bf-field { max-width: none; }
.bf-set-state { margin: 0.5rem 0 0; }
.bf-set-test { display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; margin-top: 1rem; }
.bf-set-test .bf-hint { margin: 0; }
.bf-set-sec > .bf-form > .bf-card .bf-prof-list + .bf-set-fields,
.bf-set-sec > .bf-form > .bf-card .bf-set-fields + .bf-prof-list { margin-top: 0.75rem; }

/* ── MENÚ DE LA INSTANCIA · la pestaña Menú de la ficha (chores 90.411 y 90.425) ─────────────────────────────
   El editor del menú personalizado por instancia, como ESQUEMA PLEGABLE: cada sección es un bloque con su
   baldosa de icono, su recuento y —plegada— la fila de iconos de sus módulos; desplegada, sus módulos cuelgan de
   una guía, sobre el relleno hundido (M-5). Los mandos salen al apuntar la fila; subir y bajar no ocupan sitio
   hasta que el foco llega a ella (en táctil, todo a la vista y sin asa: allí no hay arrastre, MF-4). La vista
   previa del lateral y de la Lanzadera, a la derecha desde 1100 y fija. Mobile-first; sólo tokens: conmuta con
   el tema. El marcado lo pinta instance-menu.ts. Sustituye a la columna por sección de la 90.411, que con las
   once secciones de Nido sólo dejaba ver tres. */
.bf-menued {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.bf-menued-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 14px;
  align-items: start;
}

/* LOS CANTOS, DISIMULADOS (chore 90.575): el esquema y la vista previa con el filete interior (--bf-border-soft) en vez
   del de superficie, y lo mismo sus separadores de dentro —cabeceras, pies e historial—. El token trae su valor en
   claro y en oscuro. */
.bf-menued-grid > .bf-card {
  border-color: transparent; /* 90.764: el canto es el filete del bloque de cantos, no --bf-border-soft */
}

/* La tarjeta del esquema: buscador, recuento y plegar todo arriba; las secciones debajo. */
.bf-menued-outline > header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 12px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--bf-border-soft);
}

.bf-menued-outline > header .bf-field-search {
  flex: 0 1 20rem;
}

.bf-menued-outline > header .count {
  margin-left: auto;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  font-variant-numeric: tabular-nums;
}

/* Lo que se hace sobre el menú entero, a la derecha de la cabecera y tras un filete (chore 90.435). */
.bf-menued-hacts {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-left: 12px;
  border-left: 1px solid var(--bf-border);
}

.bf-menued-board {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px;
}

.bf-menued-empty {
  margin: 0;
  padding: 20px 14px;
  text-align: center;
}

.bf-menued-keys {
  margin: 0;
  padding: 10px 14px 12px;
  border-top: 1px solid var(--bf-border-soft);
}

/* Una sección: un BLOQUE con su filete, abierta o plegada (chore 90.434; antes sólo la abierta llevaba el relleno
   hundido y las plegadas se confundían con la tarjeta). Su fondo es el del LATERAL, más transparente (chore 90.436):
   el esquema se lee como el menú que es. */
.bf-menued-sec {
  position: relative;
  border-radius: var(--bf-radius-l);
  /* Sin filete y más transparente (chore 90.665): la tarjeta se lee por su velo, no por un canto. De día ese velo
     casi no se veía sobre el panel blanco: un gris cálido suave de la tinta, que contrasta sin pesar (chore 90.675). */
  background-color: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  /* 90.764: y ahora sí con canto, el filete del buscador global, como toda tarjeta del Host. */
  box-shadow: var(--bf-lg-edge);
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

:root[data-theme="dark"] .bf-menued-sec {
  background-color: color-mix(in srgb, var(--pv-side) 28%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-menued-sec {
    background-color: color-mix(in srgb, var(--pv-side) 28%, transparent);
  }
}

.bf-menued-sec > .sec-head {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 3.25rem;
  padding: 8px 10px 8px 22px;
  border-radius: var(--bf-radius-l);
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

/* El velo de apuntar, a la mitad del de la casa (chore 90.580): 0,04 en claro y 0,055 en oscuro, lo justo para decir
   que la fila responde sin taparla. */
.bf-menued-sec > .sec-head:hover {
  background-color: color-mix(in srgb, var(--bf-hover) 50%, transparent);
}

.bf-menued-sec .chev > svg {
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-menued-sec.open > .sec-head .chev > svg,
.bf-menued-board.is-filtered .bf-menued-sec > .sec-head .chev > svg {
  transform: rotate(90deg);
}

/* La baldosa del icono de la sección: teñida de la marca (M-8), con la tinta de marca medida a 4,5:1 (M-9). */
.bf-menued .tile {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: var(--bf-radius-m);
  background-color: color-mix(in srgb, var(--bf-accent) 14%, transparent);
  color: var(--bf-selected-ink);
}

.bf-menued .tile .glyph,
.bf-menued .tile .glyph.ini {
  color: inherit;
}

/* Sin icono, la inicial dentro de la baldosa: se lee como un monograma, no como un hueco roto. */
.bf-menued .tile .glyph.ini {
  border: 0;
  font-size: var(--bf-text-ui);
  font-weight: 700;
}

.bf-menued-sec.loose .tile,
.bf-menued-sec.is-hidden .tile {
  background-color: var(--bf-hover);
  color: var(--bf-muted);
}

/* EL ÁRBOL (chore 90.699): una agrupación —una sección dentro de otra— va sangrada bajo su padre, con un hilo a su
   izquierda que la ata a él. */
.bf-menued-sec.is-nested {
  margin-inline-start: calc(var(--lvl, 1) * 1.5rem);
}

.bf-menued-sec.is-nested::before {
  content: "";
  position: absolute;
  inset-block: 0.5rem;
  inset-inline-start: -0.8rem;
  width: 2px;
  border-radius: 1px;
  background-color: var(--bf-border);
}

.bf-menued-sec.orphans .tile {
  background-color: color-mix(in srgb, var(--bf-danger) 14%, transparent);
  color: var(--bf-danger-text);
}

/* El nombre en una columna de ancho fijo y la fila de iconos justo detrás: se leen juntos, como una tabla. */
.bf-menued-sec > .sec-head .title {
  flex: 0 1 15rem;
  min-width: 0;
}

.bf-menued-sec > .sec-head h4 {
  overflow: hidden;
  font-size: var(--bf-text-body);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-sec > .sec-head .title p {
  margin-top: 1px;
  color: var(--bf-dim);
  font-size: var(--bf-text-label);
}

.bf-menued-sec.is-hidden > .sec-head h4 {
  color: var(--bf-muted);
  text-decoration: line-through;
}

/* Plegada, la fila de iconos de sus módulos (desde el corte ancho, donde cabe sin apretar el nombre). */
.bf-menued-sec .strip {
  display: none;
  flex: none;
  margin-right: auto;
  align-items: center;
  gap: 8px;
  color: var(--bf-dim);
}

.bf-menued-sec .strip .glyph,
.bf-menued-sec .strip .glyph > svg {
  width: 15px;
  height: 15px;
  color: inherit;
}

.bf-menued-sec .strip .glyph.ini {
  border: 0;
  font-size: var(--bf-text-micro);
}

.bf-menued-sec .strip small {
  font-size: var(--bf-text-micro);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.bf-menued .badges {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.bf-menued .badges .bf-pill {
  height: 1.25rem;
  padding: 0 0.5rem;
  font-size: var(--bf-text-micro);
}

/* Los mandos de una fila: a la vista al apuntarla o con el foco dentro. */
.bf-menued .acts {
  display: flex;
  align-items: center;
  gap: 2px;
}

/* APARECE Y SE VA CON SUAVIDAD (chore 90.750): fundido y una escala corta (del 94 %) desde su canto derecho, que es
   donde vive, en el tiempo de «lo que aparece en su sitio» (M-13, --bf-dur-base); se va por el mismo camino. Antes era
   sólo la opacidad en 120 ms, que se leía como un parpadeo. Con movimiento reducido, el cambio ocurre seco (abajo). */
.bf-menued-sec > .sec-head > .acts,
.bf-menued-row > .acts {
  flex: none;
  opacity: 0;
  transform: scale(0.94);
  transform-origin: right center;
  transition: opacity var(--bf-dur-base) var(--bf-ease-out), transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-menued-sec > .sec-head:hover > .acts,
.bf-menued-sec > .sec-head:focus-within > .acts,
.bf-menued-row:hover > .acts,
.bf-menued-row:focus-within > .acts,
.bf-menued-row.orphan > .acts {
  opacity: 1;
  transform: none;
}

:root[data-reduce~="motion"] .bf-menued-sec > .sec-head > .acts,
:root[data-reduce~="motion"] .bf-menued-row > .acts {
  transform: none;
  transition: none;
}

/* Los mandos de sólo icono: cuadrados, sin canto, con el velo del hover. */
.bf-menued-ibtn {
  display: inline-grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-s);
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-ibtn > svg {
  width: 16px;
  height: 16px;
  stroke-width: 1.8;
}

.bf-menued-ibtn:hover:not(:disabled) {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-menued-ibtn:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-menued-ibtn:disabled {
  opacity: 0.3;
  cursor: default;
}

.bf-menued-ibtn.off {
  color: var(--bf-warning-text);
}

.bf-menued-ibtn.danger {
  color: var(--bf-danger-text);
}

.bf-menued-ibtn.chev {
  width: 24px;
  height: 24px;
}

/* El asa: no es un botón (ver instance-menu.ts), pero se ve como uno y agarra. Sale al apuntar. */
.bf-menued .grip {
  flex: none;
  width: 20px;
  cursor: grab;
  color: var(--bf-dim);
  opacity: 0;
  transition: opacity var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued .grip:active {
  cursor: grabbing;
}

/* La fila y la cabecera se agarran enteras (chore 90.455); sus botones siguen con su mano. La cabecera, desde la
   90.580, enseña la MANO de clic: lo primero que hace es plegar y desplegar; la de agarrar queda para su asa. */
.bf-menued-row[draggable="true"] {
  cursor: grab;
}

.bf-menued-row[draggable="true"]:active {
  cursor: grabbing;
}

.bf-menued-sec > .sec-head[draggable="true"] {
  cursor: pointer;
}

.bf-menued-sec > .sec-head:hover > .grip,
.bf-menued-row:hover > .grip {
  opacity: 1;
}

/* En la cabecera, el asa va en el margen de la izquierda, delante del chevrón: no aparta la baldosa. */
.bf-menued-sec > .sec-head > .grip {
  position: absolute;
  left: 1px;
}

/* En la fila de módulo, igual: el asa sale del flujo y se va a la izquierda de la guía (chore 90.673). Dentro del flujo
   apartaba el icono 30 px del final de su rama en personalizado, y al pasar a estándar —asa oculta— el módulo saltaba
   de vuelta: las ramas quedaban sueltas en un modo y unidas en el otro. Fuera, el módulo arranca donde acaba su rama
   en los dos. */
.bf-menued-row > .grip {
  position: absolute;
  left: -38px;
}

/* Sin fila de iconos (plegada en estrecho, o abierta), los mandos y las marcas se van a la derecha. */
.bf-menued-sec > .sec-head > .badges {
  margin-left: auto;
}

.bf-menued .glyph {
  flex: none;
  display: inline-grid;
  place-items: center;
  width: 18px;
  height: 18px;
  color: var(--bf-surface-fg);
}

.bf-menued .glyph > svg {
  width: 18px;
  height: 18px;
}

.bf-menued .glyph.ini {
  border: 1px dashed currentColor;
  border-radius: var(--bf-radius-xs);
  color: var(--bf-muted);
  font-size: 10.5px;
  font-weight: 600;
  font-style: normal;
}

/* Los módulos de una sección: cuelgan de una guía bajo el chevrón. Plegada, no se pintan. */
.bf-menued-list {
  --tree-ink: var(--bf-border-strong);
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* El sangrado va entero en el padding (antes 33 px de margen + 13 de padding): el asa de la fila vive en ese hueco
     de la izquierda y el recorte de abajo la cortaría si cayese en el margen (chore 90.673). */
  margin: 0;
  padding: 0 8px 0 46px;
  /* ABRIR Y CERRAR SE ANIMA (chore 90.574), con la receta de la casa (.bf-filterbox): el alto de 0 a `auto` donde el
     navegador sabe interpolarlo, y el contenido con un fundido corto; donde no, se abre de golpe como antes. Plegada
     sigue en el árbol pero invisible —`visibility` no deja enfocar ni soltar en sus filas— y recortada con un margen
     de 4 px para que el marcador de soltar y la guía en árbol no se corten al estar abierta. */
  interpolate-size: allow-keywords;
  block-size: 0;
  opacity: 0;
  visibility: hidden;
  overflow: clip;
  overflow-clip-margin: 4px;
  transition:
    block-size var(--bf-dur-base) var(--bf-ease-out),
    padding var(--bf-dur-base) var(--bf-ease-out),
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    visibility var(--bf-dur-base) var(--bf-ease-out);
}

/* LA GUÍA EN ÁRBOL (chore 90.498): ya no es un filete del contenedor que baja hasta el fondo del bloque, sino un
   tramo POR HIJO en su ::before —├ en los de en medio, └ redondeado en el último—, así que termina en el centro del
   último hijo sea cual sea su número y se va con la sección al plegar. Cada tramo cubre su fila y el hueco de 1 px
   de debajo, para que la guía no se corte entre hijos. El «último» es el último VISIBLE: el buscador esconde filas.
   El ::after de la fila queda para el marcador de soltar. */
.bf-menued-list > .bf-menued-row::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: -1px;
  left: -13px;
  width: 12px;
  border-left: 1px solid var(--tree-ink);
  background: linear-gradient(var(--tree-ink), var(--tree-ink)) no-repeat 0 50% / 100% 1px;
  pointer-events: none;
}

.bf-menued-list > .bf-menued-row:first-child::before {
  top: -2px;
}

.bf-menued-list > .bf-menued-row:not(:has(~ .bf-menued-row:not([hidden])))::before {
  bottom: auto;
  height: 50%;
  border-bottom: 1px solid var(--tree-ink);
  border-bottom-left-radius: 6px;
  background: none;
}

.bf-menued-list > .bf-menued-row:first-child:not(:has(~ .bf-menued-row:not([hidden])))::before {
  height: calc(50% + 2px);
}

.bf-menued-sec.open > .bf-menued-list,
.bf-menued-board.is-filtered .bf-menued-sec > .bf-menued-list {
  block-size: auto;
  padding-top: 2px;
  padding-bottom: 10px;
  opacity: 1;
  visibility: visible;
}

:root[data-reduce~="motion"] .bf-menued-list {
  transition: none;
}

.bf-menued-list > .empty {
  display: grid;
  place-items: center;
  min-height: 38px;
  border: 1px dashed var(--bf-border-strong);
  border-radius: var(--bf-radius-m);
  color: var(--bf-dim);
  font-size: var(--bf-text-label);
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    border-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-board.is-filtered .bf-menued-list > .empty {
  display: none;
}

/* «Sin sección» vacía: sólo existe como hueco donde soltar mientras se arrastra un módulo. */
.bf-menued-sec.is-empty {
  display: none;
}

.bf-menued-board.drag-module > .bf-menued-sec.is-empty {
  display: block;
}

.bf-menued-sec.is-empty > .bf-menued-list {
  display: flex;
  block-size: auto;
  opacity: 1;
  visibility: visible;
  margin: 0;
  padding: 0;
  border-left: 0;
}

.bf-menued-row {
  position: relative;
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 2.375rem;
  padding: 3px 4px 3px 2px;
  border-radius: var(--bf-radius-m);
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-row:hover {
  background-color: var(--bf-hover);
}

.bf-menued-row > .glyph {
  color: var(--bf-muted);
  transition: color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-row:hover > .glyph {
  color: var(--bf-surface-fg);
}

.bf-menued-row > .name {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  font-size: var(--bf-text-ui);
  font-weight: 500;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-row > .route {
  display: none;
  flex: 1 1 0;
  min-width: 0;
  overflow: hidden;
  color: var(--bf-dim);
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-micro);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-row > .badges {
  margin-left: auto;
  flex-wrap: nowrap;
}

.bf-menued-row.is-hidden > .name {
  color: var(--bf-muted);
  text-decoration: line-through;
}

.bf-menued-row.is-hidden > .glyph {
  opacity: 0.5;
}

/* Lo que se arrastra se queda como hueco; el marcador es una raya de marca antes o después, o la cabecera
   enmarcada cuando va dentro. */
.bf-menued .is-dragging {
  opacity: 0.4;
}

.bf-menued-row.drop-before::after,
.bf-menued-row.drop-after::after,
.bf-menued-sec.drop-before::before,
.bf-menued-sec.drop-after::after {
  content: "";
  position: absolute;
  z-index: 1;
  left: 4px;
  right: 4px;
  height: 3px;
  border-radius: 999px;
  background-color: var(--bf-accent);
  pointer-events: none;
}

/* El de ANTES también en el ::after: el ::before de la fila es su tramo de la guía en árbol (chore 90.498). */
.bf-menued-row.drop-before::after {
  top: -2px;
}

.bf-menued-row.drop-after::after {
  bottom: -2px;
}

.bf-menued-sec.drop-before::before {
  top: -3px;
}

.bf-menued-sec.drop-after::after {
  bottom: -3px;
}

/* Dentro de una sección plegada (chore 90.455): el BLOQUE entero enmarcado, no sólo su cabecera — se suelta en
   cualquier sitio de él y va al final. */
.bf-menued-sec.drop-into {
  outline: 2px dashed var(--bf-accent);
  outline-offset: -1px;
}

.bf-menued-sec.drop-into > .sec-head {
  background-color: var(--bf-selected);
}

.bf-menued-sec.drop-into > .bf-menued-list > .empty {
  border-color: var(--bf-accent);
  background-color: var(--bf-selected);
  color: var(--bf-selected-ink);
}

/* Lo recién movido se marca una vez al llegar (M-13): el velo de elegido que se apaga. */
.bf-menued .landed {
  animation: bf-menued-landed var(--bf-dur-mega) var(--bf-ease-out) var(--bf-dur-fast) backwards;
}

@keyframes bf-menued-landed {
  from {
    background-color: var(--bf-selected);
    box-shadow: inset 0 0 0 1px var(--bf-accent);
  }
}

/* La vista previa (chore 90.430). Replica el marco del shell en el tema de la página: el shell pinta el lateral y la
   Lanzadera con sus propios tokens --bf-chrome-* y --bf-cc-*, que viven en su raíz de sombra y no llegan aquí, así que
   se copian sus valores —los mismos del :host de shell.ts, por tema— como tokens locales de la tarjeta. Por eso se
   quedan como colores escritos: su fuente es shell.ts, y si cambian allí hay que copiarlos otra vez. La lámina del
   panel va maciza; la del LATERAL es el cristal del shell (chore 90.450, petición del usuario: la misma transparencia
   que el lateral de verdad), con su color, su desenfoque y las mismas vías a la lámina maciza. */
/* Desde la chore 90.436 los tokens cuelgan del editor entero: los bloques de sección del esquema toman el color del
   lateral (--pv-side), más transparente. */
.bf-menued {
  --pv-fg: #1d3040;
  --pv-muted: #383d51;
  --pv-side: #f8f9fa;
  --pv-side-glass: rgba(255, 255, 255, 0.56);
  --pv-side-filter: blur(20px) saturate(1.4);
  --pv-hover: rgba(41, 63, 79, 0.056);
  --pv-line: rgba(41, 63, 79, 0.16);
  --pv-active: rgba(29, 48, 64, 0.09);
  --pv-bevel-hi: rgba(29, 48, 64, 0.11);
  --pv-bevel-lo: rgba(29, 48, 64, 0.05);
  --pv-edge: rgba(41, 63, 79, 0.1);
}

:root[data-theme="dark"] .bf-menued {
  --pv-fg: #f6f7f8;
  --pv-muted: #9da6b2;
  --pv-side: #1c2126;
  --pv-side-glass: rgba(28, 32, 36, 0.56);
  --pv-hover: rgba(174, 182, 191, 0.09);
  --pv-line: rgba(174, 182, 191, 0.16);
  --pv-active: rgba(254, 254, 254, 0.06);
  --pv-bevel-hi: rgba(254, 254, 254, 0.11);
  --pv-bevel-lo: rgba(254, 254, 254, 0.05);
  --pv-edge: rgba(174, 182, 191, 0.1);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-menued {
    --pv-fg: #f6f7f8;
    --pv-muted: #9da6b2;
    --pv-side: #1c2126;
    --pv-side-glass: rgba(28, 32, 36, 0.56);
    --pv-hover: rgba(174, 182, 191, 0.09);
    --pv-line: rgba(174, 182, 191, 0.16);
    --pv-active: rgba(254, 254, 254, 0.06);
    --pv-bevel-hi: rgba(254, 254, 254, 0.11);
    --pv-bevel-lo: rgba(254, 254, 254, 0.05);
    --pv-edge: rgba(174, 182, 191, 0.1);
  }
}

.bf-menued-preview > header {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px 10px 14px;
  border-bottom: 1px solid var(--bf-border-soft);
}

.bf-menued-preview > header h3 {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 600;
}

/* El ojo del título (chore 90.575): del tamaño y la tinta apagada de los iconos de la pastilla de al lado. */
.bf-menued-preview > header h3 > svg {
  flex: none;
  width: 15px;
  height: 15px;
  color: var(--bf-muted);
}

/* La lámina de la vista previa ocupa TODO el alto de la tarjeta (chore 90.434), tenga el menú dos secciones o
   once: es la forma del lateral y de la Lanzadera, que van de arriba abajo. */
.bf-menued-preview > .in {
  display: flex;
  flex-direction: column;
  padding: 12px;
}

.bf-menued-preview > .in > * {
  flex: 1 0 auto;
}

/* EL LATERAL: la lámina de su tema, el rótulo de sección en versalitas con el filete que se apaga hacia la derecha
   (.nav-group), los destinos de 2,4 rem con la tinta apagada y el primero como «aquí estás»: la pastilla de cristal
   con el brillo de marca naciendo por la izquierda, el bisel y la barra (M-9). */
.bf-menued-side {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 8px 12px;
  border-radius: var(--bf-radius-lg);
  background-color: var(--pv-side-glass);
  -webkit-backdrop-filter: var(--pv-side-filter);
  backdrop-filter: var(--pv-side-filter);
  box-shadow: inset 0 0 0 1px var(--pv-edge);
  color: var(--pv-muted);
}

/* Sin desenfoque, o si se pide menos transparencia, la lámina maciza: las dos vías del shell. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-menued-side,
  .bf-menued-launch {
    background-color: var(--pv-side);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-menued-side,
  .bf-menued-launch {
    background-color: var(--pv-side);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

.bf-menued-side .grp {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.bf-menued-side .hd {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.8rem 0.25rem 0.2rem;
  padding: 0.5rem 0.4rem 0;
  color: color-mix(in srgb, var(--pv-muted) 82%, transparent);
  font-size: var(--bf-text-micro);
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-side .grp:first-child .hd {
  margin-top: 0.2rem;
}

.bf-menued-side .hd::after {
  content: "";
  flex: 1;
  min-width: 0;
  height: 1px;
  background: linear-gradient(90deg, var(--pv-line), transparent);
}

.bf-menued-side .hd > span {
  overflow: hidden;
  text-overflow: ellipsis;
}

.bf-menued-side .grp.lit .hd {
  color: var(--pv-fg);
}

/* Una agrupación es un separador más del lateral (chore 90.699): el mismo rótulo, sangrado por su nivel. */
.bf-menued-side .hd.sub {
  padding-inline-start: calc(0.4rem + var(--lvl, 1) * 0.9rem);
}

.bf-menued-side .it {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  height: 2.4rem;
  padding: 0 0.75rem;
  border-radius: var(--bf-radius-btn);
  font-size: var(--bf-text-ui);
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-side .it > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-side .glyph,
.bf-menued-side .glyph > svg {
  width: 1.1rem;
  height: 1.1rem;
  color: inherit;
}

.bf-menued-side .glyph.ini {
  border: 0;
  font-size: 0.75rem;
  font-weight: 700;
}

/* Lo que se apunta en el esquema: dónde cae en el lateral, con el velo del hover del shell. */
.bf-menued-side .it.lit {
  background-color: var(--pv-hover);
  color: var(--pv-fg);
}

/* El activo, IGUAL que el del lateral del shell (chore 90.434): sus mismas cifras de brillo, bisel y barra. */
.bf-menued-side .it.current {
  background-color: var(--pv-active);
  background-image: radial-gradient(
    ellipse at left center,
    color-mix(in srgb, var(--bf-accent) 11%, transparent) 0%,
    color-mix(in srgb, var(--bf-accent) 4%, transparent) 35%,
    transparent 62%
  );
  background-repeat: no-repeat;
  background-position: left center;
  background-size: 72% 100%;
  box-shadow:
    inset 1.5px 1.5px 0 -0.5px var(--pv-bevel-hi),
    inset -1.5px -1.5px 0 -0.5px var(--pv-bevel-lo);
  color: var(--pv-fg);
  font-weight: 600;
}

.bf-menued-side .it.current > .glyph {
  color: var(--bf-accent);
}

.bf-menued-side .it.current::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0.3rem;
  width: 3px;
  height: 1.05rem;
  transform: translateY(-50%);
  border-radius: var(--bf-radius-xs, 0.25rem);
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--bf-accent) 65%, transparent),
    color-mix(in srgb, var(--bf-accent), white 15%) 50%,
    color-mix(in srgb, var(--bf-accent) 65%, transparent)
  );
}

/* LA LANZADERA: el panel con su lámina, el rótulo «Secciones» y la rejilla de tarjetas de sección de la de verdad
   (.gm-card-sec): la baldosa con el icono, el nombre y cuántos módulos, y la flecha que abre la lista. */
.bf-menued-launch {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 12px;
  border-radius: var(--bf-radius-lg);
  /* El mismo cristal que el lateral (chore 90.458), como la Lanzadera de verdad. */
  background-color: var(--pv-side-glass);
  -webkit-backdrop-filter: var(--pv-side-filter);
  backdrop-filter: var(--pv-side-filter);
  box-shadow: inset 0 0 0 1px var(--pv-edge);
  color: var(--pv-muted);
}

.bf-menued-launch .cap {
  margin: 0;
  padding: 0 0.2rem;
  color: var(--pv-muted);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.bf-menued-launch .cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9.5rem, 1fr));
  gap: 0.6rem;
  align-items: start;
}

.bf-menued-launch .card {
  border-radius: 0.8rem;
  background-color: color-mix(in srgb, var(--pv-fg) 3%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pv-fg) 7%, transparent);
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    box-shadow var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-launch .card.lit {
  box-shadow: inset 0 0 0 1px var(--bf-accent);
}

.bf-menued-launch .head {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  height: 4.2rem;
  padding: 0 0.6rem 0 0.8rem;
  border: 0;
  border-radius: 0.8rem;
  background: transparent;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-launch .head:hover {
  background-color: var(--pv-hover);
}

.bf-menued-launch .head:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-menued-launch .ctile {
  flex: none;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 0.7rem;
  background: color-mix(in srgb, var(--pv-fg) 7%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pv-fg) 9%, transparent);
  color: var(--pv-fg);
}

.bf-menued-launch .ctile .glyph,
.bf-menued-launch .ctile .glyph > svg {
  width: 1.15rem;
  height: 1.15rem;
  color: inherit;
}

.bf-menued-launch .ctile .glyph.ini {
  border: 0;
  font-size: 0.9rem;
  font-weight: 700;
}

.bf-menued-launch .ctext {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
  line-height: 1.25;
}

.bf-menued-launch .ctext b {
  overflow: hidden;
  color: var(--pv-fg);
  font-size: var(--bf-text-ui);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-launch .ctext small {
  color: var(--pv-muted);
  font-size: 0.6875rem;
}

.bf-menued-launch .head > svg {
  flex: none;
  width: 1rem;
  height: 1rem;
  rotate: 90deg; /* el galón del sprite apunta a la derecha; aquí, hacia abajo */
  color: var(--pv-muted);
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-menued-launch .card.open .head > svg {
  transform: rotate(180deg);
}

.bf-menued-launch .card.open {
  grid-column: 1 / -1;
}

.bf-menued-launch .mods {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0.4rem 0.45rem 0.45rem;
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--pv-fg) 8%, transparent);
}

.bf-menued-launch .mods[hidden] {
  display: none;
}

.bf-menued-launch .mods li {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.4rem 0.5rem;
  border-radius: var(--bf-radius-s);
  font-size: var(--bf-text-ui);
  color: var(--pv-fg);
}

.bf-menued-launch .mods li.lit {
  background-color: var(--pv-hover);
}

.bf-menued-launch .mods .glyph,
.bf-menued-launch .mods .glyph > svg {
  width: 1rem;
  height: 1rem;
  color: var(--pv-muted);
}

/* El historial, al pie del esquema. */
/* ABRIR Y CERRAR SE ANIMA (chore 90.575), con la receta de .bf-filterbox y de los apartados del panel: el alto de 0 a
   `auto` por `::details-content` e `interpolate-size`, y la línea de tiempo con un fundido corto. Donde el navegador no
   sabe, se abre de golpe como cualquier <details>. */
.bf-menued-history {
  border-top: 1px solid var(--bf-border-soft);
  interpolate-size: allow-keywords;
}

.bf-menued-history::details-content {
  block-size: 0;
  overflow-y: clip;
  transition:
    block-size var(--bf-dur-base) var(--bf-ease-out),
    content-visibility var(--bf-dur-base) var(--bf-ease-out);
  transition-behavior: allow-discrete;
}

.bf-menued-history[open]::details-content {
  block-size: auto;
}

.bf-menued-history > ol {
  opacity: 0;
  transition: opacity var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-history[open] > ol {
  opacity: 1;
}

/* La cabecera plegable, con su recuento en la pastilla neutra de la casa y el galón al otro extremo. */
.bf-menued-history > summary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 14px;
  font-size: var(--bf-text-ui);
  font-weight: 600;
  list-style: none;
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-history > summary::-webkit-details-marker {
  display: none;
}

.bf-menued-history > summary::marker {
  content: "";
}

.bf-menued-history > summary:hover {
  background-color: var(--bf-hover);
}

.bf-menued-history > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

/* El galón de los acordeones (el de .bf-filterbox), mirando abajo; al abrir da media vuelta y mira arriba. */
.bf-menued-history > summary > .chev {
  display: inline-flex;
  margin-left: auto;
  color: var(--bf-muted);
}

.bf-menued-history > summary > .chev > svg {
  flex: none;
  width: 15px;
  height: 15px;
  transform: rotate(90deg);
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-menued-history[open] > summary > .chev > svg {
  transform: rotate(-90deg);
}

@media (prefers-reduced-motion: reduce) {
  .bf-menued-history::details-content,
  .bf-menued-history > ol,
  .bf-menued-history > summary,
  .bf-menued-history > summary > .chev > svg {
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-menued-history::details-content,
:root[data-reduce~="motion"] .bf-menued-history > ol,
:root[data-reduce~="motion"] .bf-menued-history > summary,
:root[data-reduce~="motion"] .bf-menued-history > summary > .chev > svg {
  transition: none;
}

.bf-menued-history > summary > .n {
  min-width: 1.25rem;
  height: 1.125rem;
  padding: 0 0.4rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 18%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  line-height: 1.125rem;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* LA LÍNEA DE TIEMPO (chore 90.499): días como rótulos, y cada versión un NODO sobre la guía con el icono y el tono
   de su acción —guardar verde, deshacer neutro, restablecer ámbar, estándar azul, personalizado de marca—. El tono es
   sutil y nunca informa solo: la frase dice la acción (M-2). La guía sale del ::before de cada evento y se corta en
   el último del día, como el árbol del esquema (90.498). */
.bf-menued-history > ol {
  --hist-node: 1.625rem;
  list-style: none;
  margin: 0;
  padding: 0 14px 14px;
  display: flex;
  flex-direction: column;
  max-height: 16rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

.bf-menued-history li.day {
  padding: 8px 0 6px;
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.bf-menued-history li.day::first-letter {
  text-transform: uppercase;
}

.bf-menued-history li.ev {
  --tone: var(--bf-muted);
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 0 0 12px;
}

.bf-menued-history li.ev.ok { --tone: var(--bf-success); }
.bf-menued-history li.ev.warn { --tone: var(--bf-warning); }
.bf-menued-history li.ev.info { --tone: var(--bf-info); }
.bf-menued-history li.ev.accent { --tone: var(--bf-accent); }

/* La guía, del centro del nodo al del siguiente; en el último del día no se pinta. */
.bf-menued-history li.ev::before {
  content: "";
  position: absolute;
  top: var(--hist-node);
  bottom: 0;
  left: calc(var(--hist-node) / 2 - 0.5px);
  width: 1px;
  background-color: var(--bf-border-strong);
}

.bf-menued-history li.ev:not(:has(+ li.ev))::before {
  display: none;
}

.bf-menued-history li .node {
  flex: none;
  display: grid;
  place-items: center;
  width: var(--hist-node);
  height: var(--hist-node);
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--tone) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--tone) 32%, transparent);
  color: color-mix(in srgb, var(--tone) 75%, var(--bf-surface-fg));
}

.bf-menued-history li .node > svg {
  width: 0.8rem;
  height: 0.8rem;
  stroke-width: 1.8;
}

.bf-menued-history li.ev > .body {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-top: 2px;
}

.bf-menued-history li.ev > .body > p {
  margin: 0;
  font-size: var(--bf-text-label);
  line-height: 1.4;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
}

.bf-menued-history li.ev > .body > p > b {
  font-weight: 600;
  color: var(--bf-surface-fg);
}

/* El autor con sus iniciales, del tamaño de la línea. */
.bf-menued-history .av {
  display: inline-grid;
  place-items: center;
  width: 1.125rem;
  height: 1.125rem;
  margin-right: 6px;
  border-radius: 50%;
  vertical-align: -0.2rem;
  background-color: color-mix(in srgb, var(--bf-accent) 16%, transparent);
  color: var(--bf-selected-ink);
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.02em;
}

.bf-menued-history .meta {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--bf-dim);
  font-size: var(--bf-text-micro);
}

.bf-menued-history .meta > time {
  cursor: help;
}

.bf-menued-history .meta > .ver {
  padding: 0 0.35rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 14%, transparent);
  font-family: var(--bf-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Sin historial: el vacío compacto de la casa, centrado, con dos líneas que dicen qué
   aparecerá aquí (chore 90.682; antes el reloj y el texto a la izquierda). */
.bf-menued-history li.none {
  border: 1px dashed var(--bf-border-strong);
  border-radius: var(--bf-radius-m);
}

/* EL LÁPIZ, EL EDITOR DE UN ELEMENTO DE HUGIN (chore 90.452; su «más opciones»: .pop-nav y .pop-sec de
   hugin/src/front/styles/editor-inspector.css y editor-pro.css, con los tokens de Bifrost). El diálogo macizo del
   catálogo al alto de lo que lleva, con techo: sólo rueda la columna cuando se pasa. Todos los apartados JUNTOS en esa
   columna, plegables; a la izquierda, desde tres apartados, el CARRIL de iconos que lleva a cada uno. Sin carril (una
   sección) el cuadro es más estrecho. */
.bf-overlay .bf-modal.bf-menued-panel {
  width: 34rem;
}

.bf-overlay .bf-modal.bf-menued-panel.has-rail {
  width: 40rem;
}

/* La cabecera: la baldosa del icono elegido (la de los bloques de sección, algo mayor), el título y el aspa. Sin
   subtítulo con el nombre (chore 90.449): ya está en sus campos. */
.bf-menued-panel > header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 0.75rem 0.625rem 0.75rem 1.25rem;
}

.bf-menued-panel > header > .tile {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--bf-radius-l);
}

.bf-menued-panel > header > .tile[hidden] {
  display: none;
}

.bf-menued-panel > header > .tile .glyph,
.bf-menued-panel > header > .tile .glyph > svg {
  width: 20px;
  height: 20px;
}

/* Sin icono, la inicial como monograma, sin la muesca discontinua de las filas. */
.bf-menued-panel > header > .tile .glyph.ini {
  border: 0;
  color: inherit;
  font-size: var(--bf-text-ui);
  font-weight: 700;
}

.bf-menued-panel > header h3 {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-menued-x {
  display: grid;
  flex: none;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: var(--bf-radius-btn);
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out), color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-x:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-menued-x > svg {
  width: 1rem;
  height: 1rem;
}

.bf-menued-pbody {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
  min-height: 0;
}

/* El carril y la columna, uno al lado del otro; el techo es el de Hugin (34 rem o lo que deje la ventana). */
.bf-menued-pmain {
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  max-height: min(34rem, calc(100dvh - 12rem));
}

/* EL CARRIL: una tira de glifos de 1,75 rem con el MISMO aire a los dos lados y su filete de la cabecera al pie. El
   ancho va escrito también plegado, porque «auto» no se anima: al desplegar crece a la vez que los nombres. */
.bf-menued-pnav {
  display: flex;
  flex-direction: column;
  flex-shrink: 0;
  gap: 0.125rem;
  width: calc(1.75rem + 1rem + 1px + 1px);
  /* Abajo, menos aire (chore 90.733, a petición del usuario): el mando de plegar baja hasta casi el pie del modal. */
  padding: 0.875rem 0.5rem 0.25rem;
  border-right: 1px solid color-mix(in srgb, var(--bf-border) 70%, transparent);
  overflow-y: auto;
  scrollbar-width: none;
  transition: width var(--bf-dur-side) var(--bf-ease-side);
}

.bf-menued-pnav[hidden] {
  display: none;
}

.bf-menued-pnav::-webkit-scrollbar {
  display: none;
}

.bf-menued-pnav[data-open="true"] {
  width: calc(9.5rem + 1rem + 1px + 1px);
}

/* Cada apartado y el botón de plegar son la MISMA pieza: el glifo centrado por el relleno, el nombre al lado. */
.bf-menued-pnav > button {
  display: flex;
  align-items: center;
  flex-shrink: 0;
  gap: 0.5rem;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0 calc((1.75rem - 0.85rem) / 2);
  border: 0;
  border-radius: var(--bf-radius-btn);
  background: transparent;
  color: var(--bf-muted);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out),
    width var(--bf-dur-side) var(--bf-ease-side);
}

.bf-menued-pnav > button[hidden] {
  display: none;
}

.bf-menued-pnav > button > svg {
  flex-shrink: 0;
  width: 0.85rem;
  height: 0.85rem;
}

/* El nombre se ESTRECHA hasta cero, no desaparece: sigue en el árbol de accesibilidad y el gesto es uno solo. */
.bf-menued-pnav .nm {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: var(--bf-text-label);
  transition: max-width var(--bf-dur-side) var(--bf-ease-side);
}

.bf-menued-pnav[data-open="true"] > button {
  width: 100%;
}

.bf-menued-pnav[data-open="true"] .nm {
  max-width: 12rem;
}

.bf-menued-pnav > button:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-menued-pnav > button:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

/* DÓNDE ESTÁS: el tinte de marca apenas insinuado, el glifo en el acento y el rótulo a tinta entera. */
.bf-menued-pnav > .it[data-on="true"] {
  background-color: color-mix(in srgb, var(--bf-accent) 8%, transparent);
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-menued-pnav > .it[data-on="true"] > svg {
  color: var(--bf-accent);
}

/* Plegar, al FONDO del carril y con los glifos de la barra lateral; el dibujo cambia con el estado. */
.bf-menued-pnav > .tgl {
  margin-top: auto;
  color: color-mix(in srgb, var(--bf-muted) 75%, transparent);
}

.bf-menued-pnav[data-open="true"] > .tgl > .when-closed,
.bf-menued-pnav:not([data-open="true"]) > .tgl > .when-open {
  display: none;
}

/* LA COLUMNA: la que rueda, con el relleno por dentro para que la barra vaya al canto. */
.bf-menued-panes {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
  padding: 0.5rem 1.25rem 0.75rem;
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* LOS APARTADOS: separados por AIRE, sin filetes; la apertura se anima para que la vista siga al contenido. Los
   campos siguen montados aunque el apartado esté plegado: plegar no pierde lo escrito. */
.bf-menued-pane {
  margin-top: 0.375rem;
  interpolate-size: allow-keywords;
}

.bf-menued-pane:first-child {
  margin-top: 0;
}

.bf-menued-pane[hidden] {
  display: none;
}

.bf-menued-pane::details-content {
  block-size: 0;
  overflow-y: clip;
  transition:
    block-size var(--bf-dur-base) var(--bf-ease-out),
    content-visibility var(--bf-dur-base) var(--bf-ease-out);
  transition-behavior: allow-discrete;
}

.bf-menued-pane[open]::details-content {
  block-size: auto;
}

@media (prefers-reduced-motion: reduce) {
  .bf-menued-pane::details-content {
    transition: none;
  }
}

/* El rótulo: icono en el acento, versalitas espaciadas y el galón a la derecha que dice si está abierto. */
.bf-menued-pane > summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.125rem 0.5rem 0;
  border-radius: var(--bf-radius-xs);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  list-style: none;
  cursor: pointer;
  user-select: none;
}

.bf-menued-pane > summary::-webkit-details-marker {
  display: none;
}

.bf-menued-pane > summary::marker {
  content: "";
}

.bf-menued-pane > summary:hover {
  color: var(--bf-surface-fg);
}

.bf-menued-pane > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-menued-pane > summary > svg {
  flex-shrink: 0;
  width: 0.8rem;
  height: 0.8rem;
  color: var(--bf-accent);
}

.bf-menued-pane > summary > span {
  flex: 1;
  min-width: 0;
}

.bf-menued-pane > summary::after {
  content: "";
  flex-shrink: 0;
  width: 0.4rem;
  height: 0.4rem;
  margin-right: 0.15rem;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg);
  transition: transform var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-menued-pane[open] > summary::after {
  transform: rotate(45deg);
}

/* El DESTELLO del apartado al que lleva el carril: contesta al clic aunque no haya nada que rodar. */
.bf-menued-pane[data-landed] > summary {
  animation: bf-menued-pane-landed 900ms var(--bf-ease-out);
}

@keyframes bf-menued-pane-landed {
  0% {
    background-color: color-mix(in srgb, var(--bf-accent) 26%, transparent);
    color: var(--bf-surface-fg);
  }

  100% {
    background-color: transparent;
  }
}

.bf-menued-pane .pb {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  padding-bottom: 0.625rem;
}

.bf-menued-route fieldset {
  margin: 0;
  padding: 0;
  border: 0;
  min-width: 0;
}

.bf-menued-two {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 12px;
}

.bf-menued-route .bf-tabs {
  align-self: flex-start;
}

/* LOS ICONOS EN BALDOSAS (chore 90.449): cuadradas, con el dibujo solo; el nombre sale al apuntarla y sigue siendo
   el nombre accesible del radio. La herencia conserva su renglón con texto: «el del manifiesto» no se adivina por un
   dibujo. */
.bf-menued-icons {
  grid-template-columns: repeat(auto-fill, minmax(2.5rem, 1fr));
}

.bf-menued-icons label:not(.inherit) {
  justify-content: center;
  aspect-ratio: 1;
  min-height: 0;
  padding: 0;
  border-radius: var(--bf-radius-m);
}

.bf-menued-icons label:not(.inherit) > svg {
  width: 20px;
  height: 20px;
}

.bf-menued-icons label:not(.inherit) > span {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.bf-menued-panel form > footer {
  align-items: center;
}

/* En un teléfono el carril se TUMBA encima, como el de Hugin en profesional: no le quita ancho a los campos. */
@media (max-width: 619.98px) {
  .bf-menued-pmain {
    flex-direction: column;
  }

  .bf-menued-pnav,
  .bf-menued-pnav[data-open="true"] {
    flex-direction: row;
    width: auto;
    padding: 0.375rem 0.75rem;
    border-right: 0;
    border-bottom: 1px solid color-mix(in srgb, var(--bf-border) 70%, transparent);
  }

  .bf-menued-pnav > .tgl {
    display: none;
  }
}

/* ── EL ALTA DE INSTANCIA EN UN MODAL (chore 90.572) ─────────────────────────────────────────────────────────────
   El cuadro de propiedades de Hugin —el del lápiz de arriba: .bf-menued-panel, su carril y sus apartados, con los
   mismos valores— con los campos de la pantalla de alta, que llegan del servidor (ts/instances/instance-new.ts).
   Aquí sólo lo que el alta añade: la cabecera con el glifo en vez de la baldosa, los campos sueltos de un apartado
   y la marca del apartado que el servidor devolvió con errores. */
.bf-menued-panel > header > .glyph {
  display: grid;
  flex: none;
  place-items: center;
  width: 1rem;
  height: 1rem;
  color: var(--bf-accent);
}

.bf-menued-panel > header > .glyph > svg {
  width: 1rem;
  height: 1rem;
}

.bf-inew .bf-menued-panel > header {
  gap: 0.5rem;
  border-bottom: 1px solid var(--bf-border);
}

.bf-inew-fields {
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

.bf-inew .bf-menued-panes > .bf-msg {
  margin: 0.5rem 0 0.75rem;
}

/* EL APARTADO CON ERRORES: el rótulo y su icono del carril en la tinta de error, y un punto en la esquina del
   icono que se ve también con el carril plegado. El color acompaña: el error está escrito al pie de su campo (M-2). */
.bf-menued-pane[data-bad="true"] > summary,
.bf-menued-pane[data-bad="true"] > summary > svg {
  color: var(--bf-danger-text);
}

.bf-menued-pnav > .it[data-bad="true"] {
  position: relative;
  color: var(--bf-danger-text);
}

.bf-menued-pnav > .it[data-bad="true"]::after {
  content: "";
  position: absolute;
  top: 0.2rem;
  left: calc(0.45rem + 0.85rem - 0.2rem);
  width: 0.4rem;
  height: 0.4rem;
  border-radius: 50%;
  background-color: var(--bf-danger);
  box-shadow: 0 0 0 2px var(--bf-elevated-bg);
}

/* En teléfono (MF), a PANTALLA COMPLETA: el alta tiene su icono en baldosas y tres apartados, y un cuadro flotante
   dejaría una rendija. El carril se tumba encima, como el del lápiz. */
/* Excepción a MF-2: max-width porque el cuadro flotante es el del catálogo (`.bf-modal`) y la pantalla completa
   solo se pide en el teléfono. */
@media (max-width: 619.98px) {
  .bf-inew .bf-modal.bf-menued-panel {
    width: 100%;
    max-width: none;
    height: 100vh;
    height: 100dvh;
    max-height: none;
    border: 0;
    border-radius: 0;
  }

  .bf-inew .bf-menued-pmain {
    max-height: none;
  }
}

/* Un módulo creado de tipo embebido, dentro del shell de Bifrost. */
.bf-menued-embed iframe {
  display: block;
  width: 100%;
  height: 75vh;
  height: 75dvh;
  border: 0;
  background-color: var(--bf-surface-bg);
}

@media (min-width: 620px) {
  .bf-menued-two {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .bf-menued-row > .route {
    display: block;
  }
}

@media (min-width: 900px) {
  .bf-menued-sec:not(.open) > .sec-head .strip {
    display: flex;
  }

  /* Con la fila de iconos a la vista, es ella la que empuja lo demás a la derecha. */
  .bf-menued-board:not(.is-filtered) .bf-menued-sec:not(.open) > .sec-head > .badges {
    margin-left: 0;
  }

  .bf-menued-board.is-filtered .bf-menued-sec > .sec-head .strip {
    display: none;
  }
}

/* EL ALTO DE LA PÁGINA (chore 90.430; desde la 90.574 también en el corte medio y con `fillToView`, el patrón de la
   tabla de instancias): la rejilla mide lo que queda hasta el pie de la ventana (--fill-h) y el esquema y la vista
   previa ruedan cada uno por dentro —flex y min-height: 0, sin restas a mano—; la cabecera de cada tarjeta, las
   pistas y el historial se quedan. Apiladas, cada una se lleva la mitad. En teléfono, flujo normal (MF). */
@media (min-width: 620px) {
  .bf-menued-grid {
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    align-items: stretch;
    height: var(--fill-h, auto);
  }

  .bf-menued-outline > :not(.bf-menued-board),
  .bf-menued-preview > :not(.in) {
    flex: none;
  }
}

@media (min-width: 1100px) {
  .bf-menued-grid {
    grid-template-columns: minmax(0, 1fr) 24rem;
    grid-template-rows: minmax(0, 1fr);
  }
}

@media (min-width: 620px) {
  .bf-menued-outline,
  .bf-menued-preview {
    display: flex;
    flex-direction: column;
    min-height: 0;
  }

  .bf-menued-board,
  .bf-menued-preview > .in {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  /* Los vacíos, en medio del hueco y no arriba (chore 90.682): «nada coincide» se queda con el alto del tablero, y
     el de la vista previa con el de su panel. */
  .bf-menued-outline:has(> .bf-menued-empty:not([hidden])) > .bf-menued-board {
    flex: none;
  }

  .bf-menued-outline > .bf-menued-empty:not([hidden]) {
    flex: 1 0 auto;
  }

  .bf-menued-preview > .in > .bf-empty {
    min-height: 100%;
  }
}

/* Subir y bajar, con ratón: sin ancho hasta que el foco llega a la fila (se puede tabular hasta ellos, y al
   recibir el foco se abren). Con el dedo no hay arrastre: se quedan a la vista, como el resto de mandos. */
@media (hover: hover) and (pointer: fine) {
  .bf-menued .mv {
    width: 0;
    opacity: 0;
    overflow: hidden;
  }

  .bf-menued-sec > .sec-head:focus-within .mv,
  .bf-menued-row:focus-within .mv {
    width: 28px;
    opacity: 1;
  }
}

@media (pointer: coarse) {
  .bf-menued-ibtn {
    width: 44px;
    height: 44px;
  }

  .bf-menued .grip {
    display: none;
  }

  .bf-menued-sec > .sec-head > .acts,
  .bf-menued-row > .acts {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bf-menued-list,
  .bf-menued-sec,
  .bf-menued-sec > .sec-head,
  .bf-menued-sec .chev > svg,
  .bf-menued-row,
  .bf-menued-ibtn,
  .bf-menued .acts,
  .bf-menued .grip {
    transition: none;
  }

  .bf-menued .landed {
    animation: none;
  }

  /* El minimenú (chore 90.750): su regla pesa más que `.bf-menued .acts`, así que se apaga aquí con la misma. */
  .bf-menued-sec > .sec-head > .acts,
  .bf-menued-row > .acts {
    transform: none;
    transition: none;
  }
}

/* ═══ 14 · La sección General con la luz de la puerta (chores 90.692 → 90.693–90.697) ═══════════════════════
   Las cinco pantallas de General —Inicio, Mis sesiones, Mi perfil, Avisos y Ajustes— conservan la ESTRUCTURA y el
   VESTIDO de Hugin (cabecera de página, tarjetas veladas con bisel, rótulos, vacíos) y toman del inicio de sesión
   sólo lo que encaja: las cifras de la cabecera van en cápsulas, como el pie de la tarjeta del login, y el INICIO lleva
   detrás de su saludo la luz cálida, con la órbita punteada y el anillo doble del cielo, y el nombre en el degradado
   del acento. Las otras cuatro, desde la chore 90.706, sin luz ni acento: título normal, como los módulos de Hugin. Sólo en General (decisión del usuario al aprobar las maquetas de la 90.692): la administración
   sigue con la cabecera de Hugin a secas. Las piezas las escribe Ui/BifrostGeneralHead.cs. */

.bf-gen-head {
  position: relative;
  isolation: isolate;
}

/* El hijo que lleva el título cede su ancho: las cápsulas son una tira que se desliza, no algo que ensancha la
   página (teléfono sin desplazamiento lateral). */
.bf-gen-head > div:not(.acts) {
  min-width: 0;
  max-width: 100%;
}

/* EL CIELO (chore 90.714): una lámina técnica quieta, detrás del contenido de la cabecera y sin atrapar el puntero.
   Fuera del Inicio nace en la esquina de arriba a la derecha y se funde hacia la izquierda; nunca rebasa el canto
   derecho del contenido, así que no crea desplazamiento. Las tintas se templan por tema con cuatro variables: en claro,
   tinta cálida fina, nodos huecos y nítidos y una luz apenas (una mancha naranja sobre papel no es luz, es una
   mancha); en oscuro, la luz de la puerta y la órbita punteada de siempre. */
.bf-gen-sky {
  --sky-ink: color-mix(in srgb, var(--bf-surface-fg) 62%, var(--bf-door-ember));
  --sky-line: 0.26;
  --sky-floor: 0.12;
  --sky-glow: 0.4;
  position: absolute;
  z-index: -1;
  top: 0;
  right: 0;
  width: min(36rem, 72%);
  height: 100%;
  overflow: hidden;
  pointer-events: none;
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 38%);
  mask-image: linear-gradient(90deg, transparent, #000 38%);
}

:root[data-theme="dark"] .bf-gen-sky {
  --sky-ink: var(--bf-surface-fg);
  --sky-line: 0.2;
  --sky-floor: 0.09;
  --sky-glow: 0.4;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-gen-sky {
    --sky-ink: var(--bf-surface-fg);
    --sky-line: 0.2;
    --sky-floor: 0.09;
    --sky-glow: 0.4;
  }
}

/* La lámina mide lo mismo en cualquier ancho y va centrada en su caja: el núcleo cae en el centro de la caja. */
.bf-gen-sky > svg {
  position: absolute;
  top: 50%;
  left: 50%;
  display: block;
  width: 30rem;
  height: auto;
  aspect-ratio: 560 / 220;
  transform: translate(-50%, -50%);
}

.bf-gen-sky :is(ellipse, line) { fill: none; stroke-width: 1; vector-effect: non-scaling-stroke; }
.bf-gen-sky .floor ellipse { stroke: var(--sky-ink); stroke-opacity: var(--sky-floor); }
.bf-gen-sky .orbit .outer { stroke: var(--sky-ink); stroke-opacity: var(--sky-line); }
.bf-gen-sky .orbit .inner { stroke: var(--sky-ink); stroke-opacity: calc(var(--sky-line) * 0.8); stroke-dasharray: 2 4; }
.bf-gen-sky .ring-a { stroke: var(--bf-door-flame); stroke-opacity: 0.5; }
.bf-gen-sky .ring-b { stroke: var(--bf-door-ember); stroke-opacity: 0.32; }
.bf-gen-sky .edge line { stroke: var(--bf-door-flame); stroke-opacity: 0.35; }
.bf-gen-sky .tick line { stroke: var(--sky-ink); stroke-opacity: calc(var(--sky-line) * 1.6); }
.bf-gen-sky .sat circle {
  fill: var(--bf-surface-bg);
  stroke: var(--sky-ink);
  stroke-opacity: 0.55;
  stroke-width: 1;
  vector-effect: non-scaling-stroke;
}
.bf-gen-sky .sat .hot { fill: var(--bf-door-flame); stroke: none; }
.bf-gen-sky .halo { fill: none; stroke: var(--bf-door-core); stroke-opacity: 0.55; stroke-width: 1; vector-effect: non-scaling-stroke; }
.bf-gen-sky .core { fill: var(--bf-door-core); }
.bf-gen-sky .gi { stop-color: var(--bf-door-flame); stop-opacity: var(--sky-glow); }
.bf-gen-sky .gm { stop-color: var(--bf-door-ember); stop-opacity: calc(var(--sky-glow) * 0.3); }
.bf-gen-sky .go { stop-color: var(--bf-door-ember); stop-opacity: 0; }

/* En oscuro, la órbita de fuera punteada y los nodos llenos, como el cielo de la puerta. */
:root[data-theme="dark"] .bf-gen-sky .orbit .outer { stroke-width: 2.2; stroke-dasharray: 0 7; stroke-linecap: round; }
:root[data-theme="dark"] .bf-gen-sky .sat circle:not(.hot) { fill: color-mix(in srgb, var(--bf-surface-fg) 55%, var(--bf-door-core)); stroke: none; opacity: 0.6; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-gen-sky .orbit .outer { stroke-width: 2.2; stroke-dasharray: 0 7; stroke-linecap: round; }
  :root:not([data-theme="light"]) .bf-gen-sky .sat circle:not(.hot) { fill: color-mix(in srgb, var(--bf-surface-fg) 55%, var(--bf-door-core)); stroke: none; opacity: 0.6; }
}

@media (forced-colors: active) {
  .bf-gen-sky { display: none; }
}

/* En la caja del saludo del Inicio la caja lo recorta, y el cielo deja de ir en la esquina: es un hijo más de la fila,
   ENTRE el saludo y las pastillas, que se queda con el hueco que sobra. Así su centro —el núcleo— cae siempre en el
   hueco, lejos de cualquier texto o control, mida lo que mida el saludo o las pastillas. El margen de la derecha deja
   sitio al enlace de actividad, que va abajo a la derecha y sobresale de las pastillas. Se funde por los dos lados. */
.bf-home-hero.bf-gen-head {
  overflow: hidden;
}

@media (min-width: 620px) {
  .bf-home-hero.bf-gen-head > .main { order: 0; }
  .bf-home-hero.bf-gen-head > .bf-home-hero-side { order: 2; }

  .bf-home-hero.bf-gen-head > .bf-gen-sky {
    position: relative;
    top: auto;
    right: auto;
    order: 1;
    flex: 1 1 0;
    align-self: stretch;
    width: auto;
    height: auto;
    min-width: 0;
    min-height: 0;
    margin-block: -1.25rem;
    margin-inline-end: 4rem;
    /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 22%, #000 78%, transparent);
  }
}

/* EL ACENTO: una palabra del titular en el degradado de la ola, con su resplandor, como «DRAGSA» en la promesa del
   login. Con colores forzados vuelve a la tinta del sistema: un texto transparente no se leería. */
.bf-gen-head .hl {
  background: linear-gradient(100deg, var(--bf-door-mark), var(--bf-door-hl-end));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  filter: drop-shadow(0 0 0.375rem color-mix(in srgb, var(--bf-accent) 16%, transparent));
}

@media (forced-colors: active) {
  .bf-gen-head .hl {
    background: none;
    color: CanvasText;
    filter: none;
  }
}

/* LAS CÁPSULAS: el material de los datos del pie del login —velo, filete del canto y bisel—, sin desenfoque. */
.bf-gen-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.875rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-gen-facts > li {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  padding: 0.25rem 0.75rem 0.25rem 0.25rem;
  border: 1px solid color-mix(in srgb, var(--bf-elevated-edge) 45%, transparent);
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) var(--bf-door-pill-veil), transparent);
  box-shadow: inset 1px 1px 0 -0.5px var(--bf-bevel-hi), inset -1px -1px 0 -0.5px var(--bf-bevel-lo);
  font-size: var(--bf-text-ui);
  font-weight: 500;
  white-space: nowrap;
  color: var(--bf-surface-fg);
}

.bf-gen-facts > li > i {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
}

.bf-gen-facts > li > i > svg { width: 0.8rem; height: 0.8rem; }
.bf-gen-facts > li > i > .bf-led { color: var(--bf-success); }
.bf-gen-facts b { font-weight: 700; font-variant-numeric: tabular-nums; }
.bf-gen-facts > li[data-tone="warn"] > i { background-color: color-mix(in srgb, var(--bf-warning) 22%, transparent); color: var(--bf-warning-text); }
.bf-gen-facts > li[data-tone="ok"] > i { background-color: color-mix(in srgb, var(--bf-success) 20%, transparent); color: var(--bf-success-text); }
.bf-gen-facts > li[data-tone="accent"] > i { background-color: color-mix(in srgb, var(--bf-accent) 18%, transparent); color: var(--bf-accent); }

/* LAS ACCIONES SÓLO CON CAMBIOS (ts/ui/dirty-acts.ts). Sin JavaScript no hay `data-dirty-ready` y se ven siempre. */
[data-dirty-acts][data-dirty-ready]:not([data-dirty]) :is(button[data-discard], [data-dirty-only]),
[data-dirty-acts][data-dirty-ready][data-dirty-save]:not([data-dirty]) button[data-save] {
  display: none;
}

/* Por debajo del corte compacto (MF-3): el cielo, con el núcleo FUERA de la caja, más allá de la esquina de arriba
   a la derecha, para que sólo entren los arcos (en el teléfono el saludo ocupa todo el ancho y no hay hueco); las
   cápsulas, una tira deslizable en un renglón; y las acciones de la cabecera, BAJO el título y a lo ancho. */
@media (max-width: 619.98px) {
  .bf-gen-sky {
    width: 100%;
    height: 100%;
  }

  .bf-gen-sky > svg {
    top: -0.5rem;
    left: calc(100% + 7rem);
  }

  .bf-gen-facts {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
  }

  .bf-gen-facts > li {
    flex: none;
  }

  .bf-gen-head > .acts,
  .bf-page-header.bf-gen-head > .acts {
    order: 4;
    flex: 1 0 100%;
    margin-left: 0;
  }

  .bf-gen-head > .acts > :is(.bf-btn, form) {
    flex: 1 1 auto;
  }

  .bf-gen-head > .acts > form > .bf-btn {
    width: 100%;
    justify-content: center;
  }

  .bf-gen-head > .acts > .bf-btn {
    justify-content: center;
  }
}

/* Mi perfil: la píldora «PNG/JPEG · 512 KB» de la chore 90.695 se quitó en la 90.711 (no gustaba). La cámara vuelve a
   ser un disco sobre el canto del retrato, vestido de cristal más abajo, en «LOS MÓDULOS DE GENERAL». */


/* EL CRISTAL DE GENERAL (chore 90.708, rehecha a petición del usuario; desde la 90.709 sobre el material común de
   toda la app y, desde la 90.710, con el canto del centro de control). En las cinco pantallas de General las
   superficies son cristal fino: desenfoque y saturación de lo de detrás (`--bf-glass-filter`), el canto fino y
   disimulado del centro de control (`--bf-lg-edge`) y un velo más fino que el de la tarjeta (`--bf-lg-fill`), que
   deja ver a su través el degradado de la vista de siempre (`--bf-view-glow`; el usuario no quiso otra luz, 90.710).
   Nada de halos ni manchas encima del contenido, ni figuras detrás. Sin caída, que es lo que M-4 protege. Menos
   transparencia (del sistema o de la casa): macizo. */
.bf-pagebox:has(.bf-gen-head) {
  --bf-card-veil: var(--bf-lg-fill);
  --bf-card-veil-hover: var(--bf-lg-fill-hover);
}

.bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
  .bf-gen-facts > li) {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-card-veil);
  background-image: none;
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  box-shadow: var(--bf-lg-edge);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat:hover {
  background-color: var(--bf-card-veil-hover);
}

/* La mesa es UNA pieza de cristal: sus mandos, sin velo propio (el apuntado sube un peldaño) y separados por un hilo. */
.bf-pagebox:has(.bf-gen-head) .bf-home-act:not(:hover) {
  background-color: transparent;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-act + .bf-home-act {
  box-shadow: inset 1px 0 0 color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
}

/* Las piezas sin canto propio (las cartas del Inicio, «Esta sesión»): el desenfoque y el canto. */
.bf-pagebox:has(.bf-gen-head) :is(.bf-home-card > .card-preview, .bf-home-card > .card-body) {
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  box-shadow: var(--bf-lg-edge);
}

/* «ESTA SESIÓN»: el pozo de dentro de la tarjeta, mucho más fino, con su canto. */
.bf-pagebox:has(.bf-gen-head) .bf-session-here {
  background-color: color-mix(in srgb, var(--bf-panel-bg) 40%, transparent);
  box-shadow: var(--bf-lg-edge);
}

/* Menos transparencia: macizo (el desenfoque ya lo apaga --bf-glass-filter en tokens.css). */
@media (prefers-reduced-transparency: reduce) {
  .bf-pagebox:has(.bf-gen-head) {
    --bf-card-veil: var(--bf-card-bg);
    --bf-card-veil-hover: var(--bf-card-bg);
  }

  .bf-pagebox:has(.bf-gen-head) .bf-session-here { background-color: var(--bf-app-bg); }
}

:root[data-reduce~="transparency"] .bf-pagebox:has(.bf-gen-head) {
  --bf-card-veil: var(--bf-card-bg);
  --bf-card-veil-hover: var(--bf-card-bg);
}

:root[data-reduce~="transparency"] .bf-pagebox:has(.bf-gen-head) .bf-session-here { background-color: var(--bf-app-bg); }

/* LA LUZ DEL SALUDO EN LOS CANTOS (chore 90.714; rehecha a petición del usuario: más trabajada y menos saturada). El
   núcleo de la lámina del saludo es una luz, y las piezas de cristal de debajo —la mesa de accesos y las cifras— la
   recogen como lo haría un vidrio de verdad, en dos capas y nunca en naranja puro. Desde la chore 90.722 sólo la
   pieza INMEDIATA a la lámina —la mesa de accesos— y sólo por su canto de arriba y el arranque de los laterales: el
   canto de abajo mira al otro lado y no la ve.
   - un DESTELLO corto y nítido en el punto del canto más cercano a la luz (la luz cae de arriba: el canto de arriba,
     justo bajo ella), alargado a lo largo del canto;
   - un BAÑO ancho y tenue que recorre el canto de arriba y se apaga hacia las esquinas y por los laterales;
   y, dentro, un VELO apenas tibio que entra por el lado iluminado. Las tintas son la brasa de la puerta templada con
   un gris cálido (en claro) o el núcleo con blanco (en oscuro): champán, no naranja. portal.ts pone en cada pieza dónde
   cae la luz respecto a ella (`--lx`, `--ly`, px desde su esquina) y cuánto le llega (`--li`, de 0 a 1), y la marca
   con `data-lit`; sin JavaScript no hay reflejo. Es una capa ENCIMA del material de General (90.710), que no se
   toca. Menos transparencia o colores forzados: sin reflejo. */
.bf-home-desk[data-lit] {
  position: relative;
}

.bf-home-desk[data-lit] {
  --lit-tint: color-mix(in oklch, var(--bf-door-ember) 50%, var(--bf-lit-tint));
  --lit-hot: color-mix(in oklch, var(--bf-door-flame) 40%, var(--bf-relief-light));
  --lit-rim: 38%;
  --lit-glint: 70%;
  --lit-spill: 6%;
  /* El punto del canto más cercano a la luz. */
  --lit-gx: clamp(0px, var(--lx, 50%), 100%);
  --lit-gy: clamp(0px, var(--ly, 0px), 100%);
}

/* El velo de dentro va en el fondo de la propia pieza (la regla del cristal de General lo deja vacío). */
.bf-pagebox:has(.bf-gen-head) .bf-home-desk[data-lit] {
  background-image: radial-gradient(ellipse 28rem 3rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-spill)), transparent) 0%,
      transparent 72%);
}

.bf-home-desk[data-lit]::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  padding: 1px;
  border-radius: inherit;
  background:
    radial-gradient(ellipse 7.5rem 1.4rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-hot) calc(var(--li, 0) * var(--lit-glint)), transparent) 0%,
      color-mix(in srgb, var(--lit-hot) calc(var(--li, 0) * var(--lit-glint) * 0.25), transparent) 45%,
      transparent 100%),
    radial-gradient(ellipse 34rem 2.6rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-rim)), transparent) 0%,
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-rim) * 0.3), transparent) 45%,
      transparent 100%);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  pointer-events: none;
}

/* En oscuro la luz es el núcleo de la puerta templado con blanco y el filete la recoge algo más. */
:root[data-theme="dark"] .bf-home-desk[data-lit] {
  --lit-tint: color-mix(in oklch, var(--bf-door-core) 55%, var(--bf-lit-tint));
  --lit-hot: color-mix(in oklch, var(--bf-door-core) 45%, var(--bf-relief-light));
  --lit-rim: 50%;
  --lit-glint: 80%;
  --lit-spill: 5%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-home-desk[data-lit] {
    --lit-tint: color-mix(in oklch, var(--bf-door-core) 55%, var(--bf-lit-tint));
    --lit-hot: color-mix(in oklch, var(--bf-door-core) 45%, var(--bf-relief-light));
    --lit-rim: 50%;
    --lit-glint: 80%;
    --lit-spill: 5%;
  }
}

@media (prefers-reduced-transparency: reduce), (forced-colors: active) {
  .bf-home-desk[data-lit]::after { display: none; }
  .bf-pagebox:has(.bf-gen-head) .bf-home-desk[data-lit] { background-image: none; }
}

:root[data-reduce~="transparency"] .bf-home-desk[data-lit]::after { display: none; }
:root[data-reduce~="transparency"] .bf-pagebox:has(.bf-gen-head) .bf-home-desk[data-lit] { background-image: none; }

/* ═══ LOS MÓDULOS DE GENERAL, TRABAJADOS (chore 90.711) ═══════════════════════════════════════════════════════════
   Lo que la 90.691 dejó en retoques: las cinco pantallas de General con UNA gramática, la de los módulos de Hugin,
   sobre el cristal de la 90.710 (que no se toca). Las cifras de cada pantalla en cápsulas bajo su título; cada panel
   con la ficha de su glifo, su título y su pista en un bloque, separado de lo de dentro por un hilo; los renglones con
   aire; los vacíos con su órbita; la bandeja de avisos hasta abajo de la vista, como Mis sesiones (90.641). Todo lo
   de aquí cuelga de la caja de página que tiene la cabecera de General: la administración no cambia. Sin sombras en
   reposo (90.717), sin luz de cabecera ni palabra en el acento fuera del Inicio (90.706). */
.bf-pagebox:has(.bf-gen-head) {
  /* El hilo de dentro del cristal: el filete de la superficie es opaco y, sobre el velo, se leía como una raya. */
  --gen-hair: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  /* La ficha del glifo, la de las cabeceras de las fichas de instancia (`.bf-ipanel > header > .ic`). */
  --gen-tile-bg: color-mix(in srgb, var(--bf-accent) 13%, transparent);
  --gen-tile-line: color-mix(in srgb, var(--bf-accent) 22%, transparent);
}

/* ── La bandeja de Mis avisos, HASTA ABAJO DE LA VISTA: la receta de Mis sesiones (90.641), flex y no resta. La caja
   de página se lleva el alto que queda, la pantalla lo suyo, la ficha todo y su cuerpo lo que sobre, con el pie al
   fondo; el vacío se centra en ese hueco. Con muchos avisos la ficha crece y la página rueda como siempre. */
.bf-pagebox:has(> main.bf-notices) {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.bf-pagebox > main.bf-notices {
  flex: 1 1 auto;
}

.bf-notices-card {
  display: flex;
  flex: 1 1 auto;
  flex-direction: column;
}

.bf-notices-card > .bf-notices-body,
.bf-notices-body > .bf-empty {
  flex: 1 1 auto;
}

/* ── Las cabeceras de pantalla: título con más presencia, la entradilla en voz baja y las cápsulas con su aire. */
/* Ajustes lleva la cabecera de página de la administración (`.bf-page-header`); en General, su título mide lo que el de
   Mis sesiones y Avisos (`.bf-portal-head h1`): una sola gramática para las cinco. */
.bf-pagebox main > .bf-page-header.bf-gen-head > h1 {
  font-size: 27px;
  letter-spacing: -0.025em;
}

.bf-pagebox > .bf-gen-head:not(.bf-home-hero) > div > p {
  color: var(--bf-muted);
}

/* ── LOS PANELES (Mi perfil y Ajustes): la ficha del glifo, el título y la pista en un solo bloque, y un hilo que lo
   separa de los renglones (M-5). La pista cuelga bajo el título, alineada con él y no con la ficha. */
.bf-pagebox:has(.bf-gen-head) .bf-prof-panel {
  padding: 1.125rem 1.25rem 0.5rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-panel-head {
  gap: 0.75rem;
  margin-bottom: 0;
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-panel-ico {
  width: 2rem;
  height: 2rem;
  border-radius: var(--bf-radius-btn);
  background: var(--gen-tile-bg);
  box-shadow: inset 0 0 0 1px var(--gen-tile-line);
  color: var(--bf-accent-text);
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-panel-title {
  font-size: var(--bf-text-lead);
  letter-spacing: -0.005em;
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-panel-hint {
  margin: -0.375rem 0 0.25rem;
  padding: 0 0 0.875rem 2.75rem;
  border-bottom: 1px solid var(--gen-hair);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

/* AJUSTES: EL ICONO DE CADA TARJETA, EL DE HUGIN Y A RAS DEL TEXTO (chore 90.748). La ficha de 2 rem quedaba centrada
   con el título solo y la pista colgaba debajo, así que el cuadrado se leía subido respecto al bloque de texto. Hugin
   pone el icono como GLIFO delante del título, sin pastilla, de 1 rem (`h-4 w-4` de su índice de Ajustes; `.modal-icon`
   de sus diálogos: «delante de la palabra, del tamaño de la palabra y del color de la marca»): aquí igual. El glifo se
   centra con la línea del título y la pista arranca a plomo con la letra del título. Sólo Ajustes; Mi perfil conserva
   su ficha. */
.bf-pagebox:has(.bf-gen-head) .bf-set-sec .bf-prof-panel-head {
  gap: 0.5rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-set-sec .bf-prof-panel-ico {
  width: 1rem;
  height: 1rem;
  border-radius: 0;
  background: none;
  box-shadow: none;
}

.bf-pagebox:has(.bf-gen-head) .bf-set-sec .bf-prof-panel-ico > svg {
  width: 1rem;
  height: 1rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-set-sec .bf-prof-panel-hint {
  margin-top: 0.125rem;
  padding-left: 1.5rem;
}

/* Los renglones: más aire, el hilo de dentro y el rótulo a tinta media; el valor, con su peso. */
.bf-pagebox:has(.bf-gen-head) .bf-prof-list > * {
  padding: 0.8125rem 0;
  border-bottom-color: var(--gen-hair);
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-list > :last-child {
  padding-bottom: 0.8125rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-row > dt {
  font-size: var(--bf-text-ui);
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-row > dd[data-ro] {
  font-weight: 500;
}

/* ── MI PERFIL · la portada. La banda de color de Latanur era una luz de cabecera (naranja y azul) fuera del Inicio, que
   la 90.706 retiró de las demás: aquí pasa a ser la LÁMINA de la casa —una retícula de puntos fina, en la tinta del
   tema, que se funde hacia abajo y hacia la izquierda—, decoración sin color. El retrato gana un cerco de cristal y la
   cámara es un disco de cristal en su canto; el nombre, a tamaño de titular de pantalla. */
.bf-prof-hero.bf-gen-head {
  gap: 1.5rem;
  padding: 1.5rem;
}

.bf-prof-hero.bf-gen-head::before {
  height: 100%;
  background: radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--bf-surface-fg) 22%, transparent) 1px, transparent 1.25px)
    0 0 / 0.875rem 0.875rem;
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: radial-gradient(70% 130% at 100% 50%, #000 10%, transparent 75%);
  mask-image: radial-gradient(70% 130% at 100% 50%, #000 10%, transparent 75%);
}

.bf-prof-hero.bf-gen-head .bf-prof-face {
  margin-top: 0;
}

.bf-prof-hero.bf-gen-head .bf-prof-face .bf-av.lg {
  width: 6rem;
  height: 6rem;
  border: 0;
  background:
    linear-gradient(150deg, color-mix(in srgb, var(--bf-accent) 16%, transparent), transparent 70%),
    color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  box-shadow:
    0 0 0 0.3125rem color-mix(in srgb, var(--bf-surface-bg) 55%, transparent),
    0 0 0 calc(0.3125rem + 1px) var(--gen-hair);
  color: color-mix(in srgb, var(--bf-surface-fg) 78%, var(--bf-accent));
  font-size: 1.75rem;
}

.bf-prof-hero.bf-gen-head .bf-prof-cam {
  right: -0.25rem;
  bottom: -0.25rem;
  width: 2.25rem;
  height: 2.25rem;
  border: 1px solid var(--bf-lg-line);
  background-color: var(--bf-card-veil-hover);
  -webkit-backdrop-filter: var(--bf-glass-filter);
  backdrop-filter: var(--bf-glass-filter);
  box-shadow: var(--bf-lg-edge);
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-prof-hero.bf-gen-head .bf-prof-cam:hover {
  background-color: var(--bf-card-bg);
  color: var(--bf-accent-text);
}

.bf-prof-hero.bf-gen-head .bf-prof-name {
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: -0.015em;
}

.bf-prof-hero.bf-gen-head .bf-prof-mail {
  margin-top: 0.25rem;
  font-size: var(--bf-text-body);
}

.bf-prof-hero.bf-gen-head .bf-prof-tags {
  margin-top: 0.75rem;
}

/* Las cápsulas del estado de la cuenta: bajo el nombre en el teléfono; desde el corte medio, una COLUMNA a la derecha
   de la portada, que equilibra el retrato de la izquierda. Las acciones de la ficha (sólo con cambios, 90.695) se
   ponen a su lado sin caja propia: sin cambios no dejan hueco. */
/* En el teléfono envuelven en vez de deslizarse: son dos y se tienen que leer enteras. */
.bf-prof-hero.bf-gen-head > .bf-gen-facts {
  flex: 1 1 100%;
  flex-wrap: wrap;
  overflow: visible;
  margin: 0;
}

.bf-prof-hero.bf-gen-head > .bf-prof-acts {
  display: contents;
}

@media (min-width: 620px) {
  .bf-prof-hero.bf-gen-head {
    align-items: center;
  }

  .bf-prof-hero.bf-gen-head > .bf-gen-facts {
    flex: none;
    flex-direction: column;
    align-items: flex-end;
    margin-left: auto;
  }
}

/* IDENTIDAD, como una ficha de datos: desde el corte medio (MF-3) los cuatro renglones son celdas de una rejilla de dos
   —el rótulo pequeño encima y el valor debajo, a la izquierda—, con el hilo bajo la primera fila. Se lee de un vistazo
   y no deja un valor a 1.200 px de su rótulo. En el teléfono, la ficha de papel de siempre. */
@media (min-width: 620px) {
  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: 2rem;
  }

  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-row {
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0.3125rem;
    padding: 0.875rem 0;
  }

  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-list > :nth-last-child(-n + 2) {
    border-bottom: 0;
    padding-bottom: 0.875rem;
  }

  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-row > dt {
    font-size: var(--bf-text-label);
    font-weight: 500;
    color: var(--bf-muted);
  }

  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-row > dd {
    text-align: left;
    font-size: var(--bf-text-body);
  }

  .bf-pagebox:has(.bf-gen-head) #profile-form .bf-prof-row > dd.edit {
    flex: none;
    max-width: 22rem;
  }
}

/* SEGURIDAD: cada renglón con la ficha de su glifo delante del rótulo —el escudo del segundo factor, la llave de la
   contraseña— y el rótulo con su peso; la pastilla del estado, centrada con él. */
.bf-pagebox:has(.bf-gen-head) .bf-prof-sec > dt {
  align-items: center;
  gap: 0.75rem;
  min-height: 2.25rem;
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-pagebox:has(.bf-gen-head) .bf-prof-sec > dt:has(> .bf-pill) {
  align-items: center;
  gap: 0.5rem 0.75rem;
}

.bf-prof-sec-ico {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
  color: var(--bf-surface-fg);
}

.bf-prof-sec-ico > svg {
  width: 1.0625rem;
  height: 1.0625rem;
}

/* El desplegable abierto cuelga bajo el rótulo, alineado con su texto y no con la ficha. */
.bf-pagebox:has(.bf-gen-head) .bf-prof-sec .bf-prof-fold > details > summary {
  min-height: var(--bf-btn-height); /* el alto único de botón (chore 90.719) */
}

@media (min-width: 620px) {
  .bf-pagebox:has(.bf-gen-head) .bf-prof-sec .bf-prof-fold > details[open] > :not(summary) {
    margin-left: 3rem;
  }
}

/* ── MI PERFIL · desplegar sin saltos (chore 90.726, a petición del usuario). Al pulsar «Cambiar contraseña» o
   «Activar», lo que se despliega CRECE en altura con un fundido (el contenido del <details>, `::details-content`, con
   `interpolate-size` para poder ir de 0 a `auto`). El otro renglón de Seguridad se quedaba recogido hasta la 90.785
   (ver abajo). Al cerrarlo se recoge con la misma curva. Sigue siendo un <details>
   nativo y sin JavaScript; el navegador que no sepa animar `auto` lo hace seco, igual que antes. Movimiento reducido: seco. */
.bf-prof-list:has(> .bf-prof-sec .bf-prof-fold) {
  interpolate-size: allow-keywords;
}

.bf-prof-fold > details::details-content {
  block-size: 0;
  opacity: 0;
  overflow-y: clip;
  transition:
    block-size var(--bf-dur-side) var(--bf-ease-out),
    opacity var(--bf-dur-side) var(--bf-ease-out),
    content-visibility var(--bf-dur-side) allow-discrete;
}

.bf-prof-fold > details[open]::details-content {
  block-size: auto;
  opacity: 1;
}

.bf-prof-fold > details > summary {
  transition: margin-bottom var(--bf-dur-side) var(--bf-ease-out);
}

.bf-prof-list > .bf-prof-sec:has(.bf-prof-fold) {
  transition:
    block-size var(--bf-dur-side) var(--bf-ease-out),
    opacity var(--bf-dur-base) var(--bf-ease-out),
    padding-block var(--bf-dur-side) var(--bf-ease-out),
    margin-block var(--bf-dur-side) var(--bf-ease-out),
    border-width var(--bf-dur-side) var(--bf-ease-out),
    visibility var(--bf-dur-side) allow-discrete;
}

/* Chore 90.785 (petición del usuario): el OTRO renglón ya NO se recoge al abrir uno; al pulsar «Cambiar contraseña»
   la verificación en dos pasos desaparecía y no se entendía por qué. Sólo se despliega lo pulsado. */

@media (prefers-reduced-motion: reduce) {
  .bf-prof-fold > details::details-content {
    transition: none;
  }

  .bf-prof-fold > details > summary,
  .bf-prof-list > .bf-prof-sec:has(.bf-prof-fold) {
    transition: none;
  }
}

/* Separado: un pseudoelemento no vale dentro de :is() y tumbaría la regla entera. */
:root[data-reduce~="motion"] .bf-prof-fold > details::details-content {
  transition: none;
}

:root[data-reduce~="motion"] :is(.bf-prof-fold > details > summary, .bf-prof-list > .bf-prof-sec:has(.bf-prof-fold)) {
  transition: none;
}

/* ── MIS AVISOS · la leyenda de la bandeja vacía: los cinco tipos con su baldosa en su tono, en fila y envolviendo. */
.bf-notices-kinds-cap {
  margin: 1.5rem 0 0.25rem;
  font-size: var(--bf-text-mini);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-notices-kinds {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bf-notices-kinds > li {
  --nt-tone: var(--bf-off);
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.25rem 0.75rem 0.25rem 0.25rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  box-shadow: inset 0 0 0 1px var(--gen-hair, var(--bf-border));
  font-size: var(--bf-text-label);
  font-weight: 500;
  color: var(--bf-surface-fg);
}

.bf-notices-kinds > li[data-tone="bad"] { --nt-tone: var(--bf-danger); }
.bf-notices-kinds > li[data-tone="warn"] { --nt-tone: var(--bf-warning); }
.bf-notices-kinds > li[data-tone="info"] { --nt-tone: var(--bf-info); }

.bf-notices-kinds .bf-notice-tile {
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 999px;
}

.bf-notices-kinds .bf-notice-tile > svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* ── MIS SESIONES: «Esta sesión» se reconoce de un vistazo —un pozo neutro de cristal, con la luz de arriba en su
   canto superior y su baldosa con la ficha del glifo en tinta de marca—; los aparatos, con el hilo de dentro y
   baldosas algo mayores. Sin el lavado verde de la 90.711 (chore 90.720, a petición del usuario): se distingue por el
   material, no por un color de estado. */
.bf-pagebox:has(.bf-gen-head) .bf-session-here {
  padding: 0.875rem 1rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--bf-edge-hi) 70%, transparent),
    inset 0 0 0 1px var(--gen-hair);
}

.bf-pagebox:has(.bf-gen-head) .bf-session-here .bf-session-tile {
  background: var(--gen-tile-bg);
  box-shadow: inset 0 0 0 1px var(--gen-tile-line);
  color: var(--bf-accent-text);
}

.bf-pagebox:has(.bf-gen-head) :is(.bf-session-here, .bf-sess-group > summary) .bf-session-tile {
  width: 2.5rem;
  height: 2.5rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-sess-groups {
  border-top-color: var(--gen-hair);
}

.bf-pagebox:has(.bf-gen-head) .bf-sess-group {
  border-bottom-color: var(--gen-hair);
}

/* El galón, en un disco que se enciende al apuntar el aparato: dice que la fila se abre sin pesar en reposo. */
.bf-pagebox:has(.bf-gen-head) .bf-sess-group > summary > .chev {
  border-radius: 999px;
  transition:
    transform var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-pagebox:has(.bf-gen-head) .bf-sess-group > summary:hover > .chev {
  background: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  color: var(--bf-surface-fg);
}

/* ── MIS AVISOS: cada aviso es un velo fino sobre el cristal de la ficha, no un pozo opaco; lo sin leer conserva la
   luz del acento por la izquierda (90.617). */
.bf-pagebox:has(.bf-gen-head) .bf-notice {
  background: color-mix(in srgb, var(--bf-surface-fg) 3%, transparent);
  box-shadow: inset 0 0 0 1px var(--gen-hair);
}

.bf-pagebox:has(.bf-gen-head) .bf-notice:hover {
  background: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
}

.bf-pagebox:has(.bf-gen-head) .bf-notice[data-read="false"] {
  background:
    linear-gradient(90deg, color-mix(in srgb, var(--bf-accent) 9%, transparent), transparent 55%),
    color-mix(in srgb, var(--bf-surface-fg) 3%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 20%, transparent);
}

.bf-pagebox:has(.bf-gen-head) .bf-notice-tile > .bf-led {
  box-shadow: 0 0 0 2px var(--bf-card-bg);
}

/* ── LOS VACÍOS DE GENERAL: el disco del vacío con su único aro (90.257) convertido en ÓRBITA —el aro punteado del
   cielo del login—, y el título con algo más de cuerpo. Sigue siendo un aro, no una diana. */
.bf-pagebox:has(.bf-gen-head) .bf-empty:not(.sm) {
  gap: 0.375rem;
  padding-block: 3.5rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-empty:not(.sm)::before {
  width: 4rem;
  height: 4rem;
  margin-bottom: 1.25rem;
  background:
    radial-gradient(circle at 35% 30%, color-mix(in srgb, var(--bf-accent) 24%, transparent), transparent 75%),
    color-mix(in srgb, var(--bf-accent) 9%, transparent);
  outline: 1px dashed color-mix(in srgb, var(--bf-accent) 32%, transparent);
  outline-offset: 0.875rem;
  box-shadow: inset 0 0 0 1px var(--gen-tile-line);
}

.bf-pagebox:has(.bf-gen-head) .bf-empty:not(.sm)::after {
  width: 1.75rem;
  height: 1.75rem;
  margin-bottom: 1.25rem;
}

.bf-pagebox:has(.bf-gen-head) .bf-empty:not(.sm) > b {
  font-size: 1.0625rem;
  letter-spacing: -0.005em;
}

.bf-pagebox:has(.bf-gen-head) .bf-empty:not(.sm) > span {
  max-width: 26rem;
}

/* ── AJUSTES: el índice, desde el corte ancho, va SUELTO sobre la vista, sin tarjeta (chore 90.727, a petición del
   usuario: era una pieza de cristal como las del cuerpo, y una lista de secciones no es una superficie), y la sección
   puesta lleva el canto del cristal sobre su velo de seleccionado (M-9: velo, no relleno). Por debajo sigue la tira
   deslizable. */
@media (min-width: 900px) {
  .bf-pagebox:has(.bf-gen-head) .bf-set-side {
    gap: 0.125rem;
  }

  .bf-pagebox:has(.bf-gen-head) .bf-set-side > .bf-set-group {
    margin-top: 0.625rem;
    padding-top: 0.75rem;
    border-top: 1px solid var(--gen-hair);
  }
}

.bf-pagebox:has(.bf-gen-head) .bf-set-link[aria-current="page"] {
  box-shadow: var(--bf-lg-edge);
}

/* Menos transparencia: la cámara, maciza (el desenfoque ya lo apaga --bf-glass-filter). El índice ya no es tarjeta
   (90.727): no tiene fondo que endurecer. */
@media (prefers-reduced-transparency: reduce) {
  .bf-pagebox:has(.bf-gen-head) .bf-prof-cam { background-color: var(--bf-card-bg); }
}

:root[data-reduce~="transparency"] .bf-pagebox:has(.bf-gen-head) .bf-prof-cam { background-color: var(--bf-card-bg); }

/* Colores forzados: la retícula de la portada es decoración y se va; los discos y las fichas recuperan su canto. */
@media (forced-colors: active) {
  .bf-prof-hero.bf-gen-head::before { display: none; }
  .bf-pagebox:has(.bf-gen-head) :is(.bf-prof-panel-ico, .bf-prof-face .bf-av.lg) { border: 1px solid CanvasText; }
}

@media (prefers-reduced-motion: reduce) {
  .bf-pagebox:has(.bf-gen-head) :is(.bf-prof-cam, .bf-sess-group > summary > .chev) { transition: none; }
}

:root[data-reduce~="motion"] .bf-pagebox:has(.bf-gen-head) :is(.bf-prof-cam, .bf-sess-group > summary > .chev) { transition: none; }

/* ═══ USUARIOS, PREMIUM (chore 90.735) Y SUS DOS SECCIONES (chore 90.738) ═════════════════════════════════════════
   El módulo de Usuarios con el vestido de los módulos trabajados (la ficha de instancia de la 90.716): la banda de
   filtros y la tabla —o la matriz— en el CRISTAL FINO, sin caída en reposo (90.717) y con el canto de siempre; la banda
   de filtros mejor leída, con el estado en pastillas; y las dos secciones del módulo —Personas · Asignaciones— en las
   pestañas de la casa con una línea que dice para qué es cada una. Todo cuelga de `.bf-usections` y `.bf-users`, que
   sólo pintan estas dos pantallas, y de `.bf-upanel`, el modal con navegador de apartados de Usuarios. */

/* ── Las secciones: las pestañas bajo la cabecera y, pegada a ellas, su para qué ── */
.bf-usections {
  margin-bottom: -0.375rem;
}

.bf-usections-lead {
  max-width: 72ch;
  margin: 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

/* ── El material: el cristal fino de la ficha de instancia, en la banda y en la tarjeta que filtra ── */
.bf-users > .bf-toolbar:has(> .bf-filterbox),
.bf-users > .bf-card {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-users > .bf-toolbar:has(> .bf-filterbox),
  .bf-users > .bf-card {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-users > .bf-toolbar:has(> .bf-filterbox), .bf-users > .bf-card) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-users > .bf-toolbar:has(> .bf-filterbox),
  .bf-users > .bf-card {
    border-color: CanvasText;
  }
}

/* La banda y su tabla son UNA pieza: el filete que las une, el fino del cristal. */
.bf-users > .bf-toolbar:has(> .bf-filterbox) .bf-filterbox[open] > .bf-filterbox-body {
  border-top: 1px solid var(--bf-lg-line);
}

/* ── La banda de filtros, mejor leída: el buscador a lo ancho y los ejes debajo, con aire ── */
.bf-users .bf-filterbox-body {
  align-items: center;
  gap: 0.75rem 1.75rem;
  padding-top: 0.75rem;
}

.bf-users .bf-filterbox-body .bf-field-search {
  flex: 1 1 100%;
  width: auto;
}

/* «Limpiar», al otro extremo y con su glifo: deshace, no navega. */
.bf-users .bf-filterbox-body .bf-fclear {
  margin-left: auto;
}

/* EL ESTADO EN PASTILLAS: un carril de mando con tres opciones; la elegida, con el velo de seleccionado y la tinta de
   marca (M-9: «esto está elegido», no relleno naranja), y cada una con su punto de color y su nombre (M-2). Son radios de
   verdad: el foco se ve en la pastilla y las flechas cambian de opción, como en cualquier grupo de radios. */
.bf-fchips {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.bf-fchips > legend {
  float: left;
  padding: 0;
  font-size: var(--bf-text-label);
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

.bf-fchips > .row {
  display: inline-flex;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: 999px;
  background-color: var(--bf-control-bg);
}

.bf-fchips label {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  height: calc(var(--bf-control-height) - 0.25rem);
  padding: 0 0.75rem;
  border-radius: 999px;
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-fchips input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.bf-fchips label:hover {
  color: var(--bf-surface-fg);
  background-color: var(--bf-hover);
}

.bf-fchips label:has(input:checked) {
  background-color: var(--bf-selected);
  color: var(--bf-selected-ink);
  font-weight: 600;
}

.bf-fchips label:has(input:focus-visible) {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
}

.bf-fchips label > i {
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background-color: color-mix(in srgb, var(--bf-muted) 60%, transparent);
}

.bf-fchips label.ok > i {
  background-color: var(--bf-success);
}

/* «Todos»: los dos puntos juntos, el de activo y el de baja. */
.bf-fchips label.all > i {
  background: linear-gradient(90deg, var(--bf-success) 50%, color-mix(in srgb, var(--bf-muted) 60%, transparent) 50%);
}

@media (forced-colors: active) {
  .bf-fchips label:has(input:checked) {
    outline: 2px solid Highlight;
  }
}

/* ── El modal de Usuarios con navegador de apartados (alta, importación y acciones masivas) ── */
.bf-upanel .bf-upanel-lead {
  margin: 0.375rem 0 0.75rem;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

.bf-upanel .bf-menued-pbody > footer {
  align-items: center;
}

.bf-upanel .bf-menued-pane .pb > .bf-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.bf-upanel .bf-menued-pane .pb > .bf-form > p {
  margin: 0;
}

/* LOS CANTOS DEL CRISTAL (chore 90.741, a petición del usuario). Las tarjetas de cristal dibujan su canto con luz de
   arriba: el de ARRIBA, un tono más claro que su fondo, se enciende en la esquina de arriba a la izquierda —la dobla—
   y se apaga hacia la derecha; el de ABAJO, al revés, se enciende hacia la derecha y dobla la esquina de abajo a la
   derecha; los LATERALES, entre esquina y esquina, un tono más oscuro. Para que el canto doble las esquinas es un
   ANILLO de un píxel (`::after`, recortado al canto con una máscara, como el reflejo del saludo) y no tiras rectas:
   la franja de arriba y la de abajo miden lo que el radio (`--bf-edge-r`), así cubren el arco; los laterales, el
   resto. Debajo del anillo, el borde propio es el filete del buscador global y sus filtros (`--bf-edge-line`, chore
   90.744): el canto es la MEZCLA de los dos, y en claro se nota aunque el blanco no destaque sobre el papel. Sustituye al bisel interior (`--bf-lg-edge`)
   en estas piezas. Colores forzados: el borde del sistema. */
:root {
  /* El anillo: desde la chore 90.760, el filete del buscador global y nada más —IDÉNTICO a él—. Las luces de
     esquina y los laterales oscuros de las 90.741/90.758 se retiraron a petición del usuario. */
  --bf-edge-layers:
    linear-gradient(var(--bf-edge-line), var(--bf-edge-line));
}

:is(.bf-card, .bf-iglass, .bf-pcard, .bf-kpi, .bf-sect, .bf-prodstrip > a),
.bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
  .bf-gen-facts > li) {
  /* Desde la 90.758, sin tramos que corten: dos luces RADIALES nacen en las esquinas de arriba a la izquierda y de
     abajo a la derecha y se apagan a lo largo del canto (todo su ancho) y por el lateral (algo más de la mitad del
     alto); la sombra de los laterales se desvanece hacia arriba y hacia abajo y se funde con ellas. */

  position: relative;
  border: 1px solid transparent;
  background-image: none;
  background-color: var(--bf-card-veil);
  box-shadow: none;
  /* EL FILETE, POR CONTORNO (chore 90.764): el anillo `::after` va a `inset: -1px`, sobre el borde, y casi todas
     estas piezas recortan su contenido (`overflow: hidden`, por las esquinas de la tabla): el recorte se comía el
     anillo y la tarjeta se quedaba SIN canto (las secciones de Ajustes, las fichas, las licencias…). El contorno no
     lo recorta el `overflow` de la propia pieza, sigue el radio y no ocupa sitio: a -1 px cae justo sobre el borde
     transparente. El anillo queda sólo para las luces (relieve, la lámina del saludo), sin filete debajo. */
  outline: 1px solid var(--bf-edge-line);
  outline-offset: -1px;
  --bf-edge-layers: linear-gradient(transparent, transparent);
}

/* Las tarjetas de ítem siguen llevando el filete en su anillo (no recortado), también dentro de una de estas. */
:is(.bf-hcard, .bf-home-card) {
  --bf-edge-layers: linear-gradient(var(--bf-edge-line), var(--bf-edge-line));
}

/* LA BANDA DE FILTROS (chore 90.764): suelta o soldada a su tabla, también lleva el filete. Soldada, su contorno
   cierra por abajo y ése es el filete que las une; la tarjeta de debajo no repite el de arriba: sólo laterales y pie,
   por borde. Dentro de una tarjeta sigue siendo sólo la banda, sin caja. */
.bf-toolbar:has(> .bf-filterbox):not(.bf-card > *) {
  outline: 1px solid var(--bf-edge-line);
  outline-offset: -1px;
}

.bf-toolbar:has(> .bf-filterbox):not(.bf-card > *) + .bf-card.bf-card {
  outline: 0;
  border: 1px solid var(--bf-edge-line);
  border-top: 0;
}

/* «Esta sesión», el pozo de dentro de la tarjeta, y el cajón lateral: el mismo filete, sin la ceja de luz. */
.bf-session-here,
.bf-pagebox:has(.bf-gen-head) .bf-session-here.bf-session-here {
  box-shadow: var(--bf-lg-edge);
}

.bf-drawer {
  border-left-color: var(--bf-edge-line);
}

:is(.bf-card, .bf-iglass, .bf-pcard, .bf-kpi, .bf-sect, .bf-prodstrip > a)::after,
.bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
  .bf-gen-facts > li)::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 1;
  padding: 1px;
  border-radius: inherit;
  background: var(--bf-edge-layers);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
  pointer-events: none;
}

/* LAS TARJETAS DE ÍTEM (instancias, productos, cartas del Inicio): no tienen borde propio y su canto ya vive en su
   `::after` (con el reflejo que sigue al puntero cuando llevan relieve). Ese mismo anillo lleva ahora el canto del
   cristal debajo del reflejo; el filo de color del producto (`--hc-edge`) se conserva, el bisel no. */
:is(.bf-hcard, .bf-home-card)::after {
  padding: 1px;
  background: var(--bf-edge-layers);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}

.bf-hcard::after { box-shadow: inset 0 0 0 1px var(--hc-edge, transparent); }
.bf-home-card::after { box-shadow: none; }

:is(.bf-home-card, .bf-hcard)[data-relief]::after {
  background:
    radial-gradient(circle 8rem at var(--lx, 50%) var(--ly, 0%),
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 100%), transparent) 0%,
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 0.4 * 100%), transparent) 40%,
      color-mix(in srgb, var(--rim-c) 0%, transparent) 100%),
    var(--bf-edge-layers);
}

/* EL HOVER DE LAS CIFRAS DEL INICIO (chore 90.759; desde la 90.766, EXACTAMENTE el de las tarjetas de estado de las
   instancias, `.bf-home-card[data-relief]`, a petición del usuario: la sombra de antes era exagerada). Mismos números
   del relieve: sin sombra; en claro la cifra SUBE 3 px (en oscuro no), se inclina 2° sólo mientras el puntero se
   mueve (`data-moving` de ui/relief.ts) y su canto recoge una luz blanca que sigue al puntero (`--rim`, encendida con
   `--rim-on`), con un halo tenue por dentro. Sin armar (táctil o menos movimiento), el apuntado de siempre. */
.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief] {
  --settle: 600ms cubic-bezier(0.22, 1, 0.36, 1);
  --rim-c: var(--bf-relief-light);
  --rim: 1;
  --halo: 0.2;
  --lift: -3px;
  transition: transform var(--settle), translate var(--settle), background-color var(--bf-dur-base) var(--bf-ease-out);
}

:root[data-theme="dark"] .bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief] { --rim: 0.18; --halo: 0.015; --lift: 0px; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief] { --rim: 0.18; --halo: 0.015; --lift: 0px; }
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]:hover {
  --settle: 280ms var(--bf-ease-out);
  translate: 0 var(--lift, 0px);
  transform: none; /* sin el apuntado de 2 px de la cifra suelta: el relieve manda */
  box-shadow: none;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-moving]:hover {
  transform: perspective(900px) rotateX(calc(var(--dy, 0) * 2deg)) rotateY(calc(var(--dx, 0) * -2deg));
}

/* Es una carta, no un enlace de texto: sin subrayado al pasar. */
.bf-pagebox:has(.bf-gen-head) a.bf-home-stat:hover,
.bf-pagebox:has(.bf-gen-head) a.bf-home-stat:hover * { text-decoration: none; }

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]::before {
  box-shadow: none;
  background: radial-gradient(circle 9rem at var(--lx, 50%) var(--ly, 0%), rgb(255 255 255 / var(--halo)), transparent 70%);
  opacity: 0;
  transition: opacity 320ms var(--bf-ease-out);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]:hover::before { opacity: 1; }

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]::after {
  background:
    radial-gradient(circle 8rem at var(--lx, 50%) var(--ly, 0%),
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 100%), transparent) 0%,
      color-mix(in srgb, var(--rim-c) calc(var(--rim) * var(--rim-on) * 0.4 * 100%), transparent) 40%,
      color-mix(in srgb, var(--rim-c) 0%, transparent) 100%),
    var(--bf-edge-layers);
  transition: --rim-on 320ms var(--bf-ease-out);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]:hover::after { --rim-on: 1; }

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief]:active {
  --settle: 90ms var(--bf-ease-out);
  translate: none;
  transform: none;
}

/* Las piezas que recortan su contenido (la mesa y la caja del saludo) recortarían también el anillo, que va sobre el
   borde: recortan un píxel más allá de su relleno, justo hasta el canto. */
.bf-pagebox:has(.bf-gen-head) :is(.bf-home-desk, .bf-home-hero.bf-gen-head) {
  overflow: clip;
  overflow-clip-margin: 1px;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat:hover {
  background-color: var(--bf-card-veil-hover);
}

/* La mesa encendida por la lámina del saludo (90.714/90.722): su anillo ya es el del reflejo; lleva encima el
   reflejo y debajo el canto, y su velo tibio dentro. */
.bf-pagebox:has(.bf-gen-head) .bf-home-desk[data-lit] {
  background-image: radial-gradient(ellipse 28rem 3rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-spill)), transparent) 0%,
      transparent 72%);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-desk[data-lit]::after {
  inset: -1px;
  background:
    radial-gradient(ellipse 7.5rem 1.4rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-hot) calc(var(--li, 0) * var(--lit-glint)), transparent) 0%,
      color-mix(in srgb, var(--lit-hot) calc(var(--li, 0) * var(--lit-glint) * 0.25), transparent) 45%,
      transparent 100%),
    radial-gradient(ellipse 34rem 2.6rem at var(--lit-gx) var(--lit-gy),
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-rim)), transparent) 0%,
      color-mix(in srgb, var(--lit-tint) calc(var(--li, 0) * var(--lit-rim) * 0.3), transparent) 45%,
      transparent 100%),
    var(--bf-edge-layers);
}

@media (forced-colors: active) {
  :is(.bf-card, .bf-iglass, .bf-pcard, .bf-kpi, .bf-sect, .bf-prodstrip > a),
  .bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
    .bf-gen-facts > li) {
    border-color: CanvasText;
  }

  :is(.bf-card, .bf-iglass, .bf-pcard, .bf-kpi, .bf-sect, .bf-prodstrip > a, .bf-hcard, .bf-home-card)::after,
  .bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
    .bf-gen-facts > li)::after {
    display: none;
  }
}

/* ═══ SESIONES COMO «MIS SESIONES» Y LA BARRA DE LOS SELECCIONADOS (chore 90.737) ═══════════════════════════════════
   El visor de sesiones de la administración con el vestido de «Mis sesiones»: la cabecera con su entradilla y las
   cifras en cápsulas (`.bf-gen-facts`, sin luz ni palabra en el acento), la banda y la tabla en el cristal fino de los
   módulos trabajados —sin caída en reposo—, cada sesión con la baldosa de su aparato y el cerrar como un icono
   discreto. La selección la lleva `.bf-selbar`, la barra inferior de documentos de Vor: una pastilla de cristal que
   flota al pie de la columna con el número en su disco, el atajo de la página, el mando del lote y el aspa. Todo
   cuelga de `.bf-asess` (sólo esta pantalla); `.bf-selbar` es pieza y la puede usar cualquier lista con lotes. */

/* ── La cabecera: la entradilla bajo el título ── */
.bf-asess-head .bf-asess-lead {
  max-width: 72ch;
  margin: 0.25rem 0 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  line-height: 1.5;
}

.bf-asess-head .bf-gen-facts {
  margin-top: 0.75rem;
}

.bf-asess-head > .acts {
  align-self: flex-end;
}

/* ── El material: el cristal fino, en la banda y en la tarjeta de la tabla ── */
.bf-asess > .bf-toolbar:has(> .bf-filterbox),
.bf-asess > .bf-card {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

.bf-asess > .bf-toolbar:has(> .bf-filterbox) .bf-filterbox[open] > .bf-filterbox-body {
  border-top: 1px solid var(--bf-lg-line);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-asess > .bf-toolbar:has(> .bf-filterbox),
  .bf-asess > .bf-card {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-asess > .bf-toolbar:has(> .bf-filterbox), .bf-asess > .bf-card) {
  background-color: var(--bf-card-bg);
}

/* ── La fila: el aparato con su baldosa, como en «Mis sesiones» ── */
.bf-asess-dev {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-width: 0;
}

.bf-asess-dev > .bf-session-tile {
  width: 2.25rem;
  height: 2.25rem;
  color: var(--bf-surface-fg);
}

.bf-asess-dev .bf-session-device {
  font-size: var(--bf-text-ui);
}

.bf-asess-dev .bf-session-meta {
  gap: 0.125rem 0.75rem;
}

/* La luz de la esquina recorta contra la superficie de la tabla, no contra el pozo de «Mis sesiones». */
.bf-asess-dev .bf-session-tile > .lamp {
  box-shadow: 0 0 0 2px var(--bf-card-bg);
}

/* La última actividad con su LED (M-2: el color con la hora escrita al lado). */
.bf-asess-seen {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.bf-asess-seen > .bf-led:not(.ok) {
  color: var(--bf-off);
}

/* El cerrar de la fila: el de «Mis sesiones», que se revela al apuntar o enfocar la fila (en táctil, tenue siempre). */
.bf-asess td.right > form {
  display: inline-flex;
  justify-content: flex-end;
}

.bf-asess tr:is(:hover, :focus-within, .sel) .bf-sess-x {
  opacity: 1;
  transform: none;
}

/* ── LA BARRA DE LOS SELECCIONADOS (la de documentos de Vor) ──
   Flota sobre el pie de la ventana, centrada en su columna, y se queda a mano mientras se rueda. Va FIJA y no
   `sticky` como en Vor: aquí el panel de la administración es un contenedor de desplazamiento que no rueda (rueda el
   documento), y una pegada se quedaba debajo de la tabla, fuera de la vista. El centro de la columna lo pone el
   módulo en `--bf-selbar-x`; sin él, el de la ventana. El cristal es el de la cabecera del shell, como el de los avisos
   emergentes, con el canto del centro de control; la caída corta es la de lo que FLOTA (M-4). Entra subiendo con un
   pelo de rebote —llega, y eso dice que marcar ha abierto algo— y se va bajando y desvaneciéndose. */
.bf-selbar {
  position: fixed;
  bottom: calc(1.25rem + env(safe-area-inset-bottom, 0px));
  left: var(--bf-selbar-x, 50%);
  translate: -50% 0;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 0.25rem;
  width: max-content;
  max-width: calc(100vw - 2rem);
  margin: 0;
  padding: 0.375rem;
  border-radius: 999px;
  background: var(--bf-head-glass);
  -webkit-backdrop-filter: var(--bf-head-filter);
  backdrop-filter: var(--bf-head-filter);
  box-shadow: 0 6px 16px -6px color-mix(in srgb, var(--bf-shadow-ink) 18%, transparent), var(--bf-glass-bevel);
  color: var(--bf-surface-fg);
  font-size: var(--bf-text-ui);
  animation: bf-selbar-in calc(var(--bf-dur-side) * 1.15) var(--bf-ease-spring);
}

.bf-selbar[hidden] {
  display: none;
}

/* Mientras flota, la lista deja sitio al pie: la barra no tapa el paginador al llegar abajo. */
.bf-asess.has-selbar {
  padding-bottom: 4.5rem;
}

.bf-selbar.is-leaving {
  animation: bf-selbar-out var(--bf-dur-base) var(--bf-ease-side) forwards;
  pointer-events: none;
}

@keyframes bf-selbar-in {
  from {
    opacity: 0;
    transform: translateY(18px) scale(0.94);
  }
}

@keyframes bf-selbar-out {
  to {
    opacity: 0;
    transform: translateY(14px) scale(0.96);
  }
}

/* Los grupos, teñidos sobre el cristal. */
.bf-selbar-grp {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  gap: 0.125rem;
  padding: 0.125rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
}

/* CUÁNTAS: el número en su disco de acento y la palabra al lado; cifras tabulares, para que no baile. */
.bf-selbar-count {
  gap: 0.4375rem;
  padding: 0.125rem 0.75rem 0.125rem 0.25rem;
  font-weight: 600;
  white-space: nowrap;
}

.bf-selbar-num {
  display: inline-grid;
  place-items: center;
  min-width: 1.375rem;
  height: 1.375rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  background: var(--bf-accent);
  color: var(--bf-on-cta);
  font-size: var(--bf-text-label);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.bf-selbar-num.is-bump {
  animation: bf-selbar-num var(--bf-dur-thumb) var(--bf-ease-spring);
}

@keyframes bf-selbar-num {
  from { transform: scale(0.6); }
}

/* Los mandos: redondos, sin caja, en tinta apagada, y el velo al apuntar. */
.bf-selbar .bf-selbar-act {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.875rem;
  height: 1.875rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: transparent;
  box-shadow: none;
  color: var(--bf-muted);
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-selbar .bf-selbar-act > svg {
  width: 1rem;
  height: 1rem;
}

.bf-selbar .bf-selbar-act:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 11%, transparent);
  color: var(--bf-surface-fg);
}

.bf-selbar .bf-selbar-act:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 1px;
}

/* La acción del lote, en rojo sin caja, y rojo también al apuntarla. */
.bf-selbar .bf-selbar-act.danger,
.bf-selbar .bf-selbar-act.danger:hover:not(:disabled) {
  color: var(--bf-danger-text);
}

.bf-selbar .bf-selbar-act.danger:hover:not(:disabled) {
  background-color: color-mix(in srgb, var(--bf-danger) 14%, transparent);
}

/* El atajo para llevarse la página: texto en la tinta de marca, sin caja hasta apuntarlo. */
.bf-selbar .bf-selbar-all {
  height: 1.625rem;
  padding: 0 0.625rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--bf-selected-ink);
  font: inherit;
  font-size: var(--bf-text-label);
  font-weight: 600;
  cursor: pointer;
}

.bf-selbar .bf-selbar-all:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 11%, transparent);
}

.bf-selbar .bf-selbar-all[hidden] {
  display: none;
}

/* El separador de un pelo antes del aspa. */
.bf-selbar-sep {
  flex: none;
  width: 1px;
  height: 1.25rem;
  margin: 0 0.125rem;
  background: var(--bf-border);
}

/* En estrecho, sin la palabra ni el atajo: la pastilla entera cabe en una línea. Y por ENCIMA de la barra inferior
   del shell, que en el teléfono flota sobre la página (los mismos 80 px que reserva `scroll-padding-bottom`). */
@media (max-width: 619.98px) {
  .bf-selbar {
    bottom: calc(80px + 0.5rem + env(safe-area-inset-bottom, 0px));
  }

  .bf-selbar-count > .bf-selbar-word,
  .bf-selbar .bf-selbar-all {
    display: none;
  }

  .bf-selbar-count {
    padding-right: 0.25rem;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-selbar {
    background: var(--bf-elevated-bg);
  }
}

@media (prefers-reduced-motion: reduce) {
  .bf-selbar,
  .bf-selbar.is-leaving,
  .bf-selbar-num.is-bump {
    animation: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-selbar, .bf-selbar.is-leaving, .bf-selbar-num.is-bump) {
  animation: none;
}

@media (forced-colors: active) {
  .bf-selbar {
    border: 1px solid CanvasText;
  }
}

/* ═══ AUTENTICACIÓN DE TERCEROS, REHECHA, Y SUS BOTONES EN EL ACCESO (chore 90.736) ═════════════════════════════════
   Dos piezas de la misma chore. En la ADMINISTRACIÓN, el listado con el vestido de los módulos trabajados: cabecera con
   entradilla y cápsulas, la nota del acceso local serena, y cada proveedor en una tarjeta de cristal fino —sin caída en
   reposo— con su marca en baldosa y, debajo, su botón tal y como saldrá en el acceso. En el ACCESO, los botones de
   terceros como los del login antiguo —el dibujo del proveedor delante del «Iniciar sesión con …»— hechos con el
   material de la puerta: la píldora de cristal de la tarjeta, la marca en su baldosa y la flecha que se adelanta al
   apuntar, como la del botón principal. La marca la escribe Identity/External/ExternalIdpBrand.cs. */

/* ── La cabecera del listado ── */
.bf-idps-head .bf-idps-lead {
  max-width: 72ch;
  margin: 0.25rem 0 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  line-height: 1.5;
}

.bf-idps-head .bf-gen-facts {
  margin-top: 0.75rem;
}

.bf-idps-head > .acts {
  align-self: flex-end;
}

/* ── La nota del acceso local: una garantía, no una alarma ── */
.bf-idps-note {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin: 0;
  padding: 0.625rem 0.875rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: var(--bf-radius-l);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  line-height: 1.45;
}

.bf-idps-note > svg {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--bf-success-text);
}

/* ── Las tarjetas: el cristal fino de los módulos trabajados ── */
.bf-idps > .bf-pcard {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  box-shadow: var(--bf-lg-edge);
}

/* El que no se ofrece conserva su canto discontinuo (chore 90.074), ahora sobre el cristal. */
.bf-idps > .bf-pcard.off {
  border-color: color-mix(in srgb, var(--bf-warning) 45%, transparent);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-idps > .bf-pcard,
  .bf-idps-note {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-idps > .bf-pcard, .bf-idps-note) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-idps > .bf-pcard,
  .bf-idps-note {
    border-color: CanvasText;
  }
}

.bf-idps > .bf-pcard .top {
  align-items: center;
  padding: 1rem 1rem 0.75rem;
}

.bf-idps > .bf-pcard h3 {
  margin: 0;
  font-size: var(--bf-text-body);
  line-height: 1.3;
}

.bf-idps > .bf-pcard h3 > a {
  color: inherit;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.bf-idps > .bf-pcard h3 > a:hover,
.bf-idps > .bf-pcard h3 > a:focus-visible {
  text-decoration: underline;
  text-underline-offset: 0.18em;
}

/* La MARCA en su baldosa: neutra, como la del aparato de «Mis sesiones»; el dibujo en la tinta del texto. */
.bf-idps-brand {
  display: contents;
}

.bf-idps-brand > .mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 9%, transparent);
  color: var(--bf-surface-fg);
}

.bf-idps-brand > .mark > svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* ASÍ SALE EN EL ACCESO: el botón en pequeño, sin vida, con su rótulo encima. */
.bf-idps-preview {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  padding: 0 1rem 0.875rem;
}

.bf-idps-preview > .cap {
  font-size: var(--bf-text-mini);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-idps-btn {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
  height: 2.25rem;
  padding: 0 0.75rem 0 0.25rem;
  border: 1px solid var(--bf-button-line);
  border-radius: var(--bf-button-radius);
  background-color: var(--bf-button-bg);
  color: var(--bf-button-fg);
  font-size: var(--bf-text-ui);
  font-weight: 600;
}

.bf-idps-btn > .mark {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: calc(var(--bf-button-radius) - 3px);
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
}

.bf-idps-btn > .mark > svg {
  width: 1rem;
  height: 1rem;
}

.bf-idps-btn > .nm {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-idps > .bf-pcard .foot {
  border-top-color: var(--bf-lg-line);
}

/* ── LOS BOTONES DE TERCEROS EN EL ACCESO ── */
.bf-door nav.external {
  gap: 0.625rem;
}

.bf-door nav.external > .bf-sep {
  margin-bottom: 0.125rem;
}

.bf-door nav.external .bf-idp {
  justify-content: flex-start;
  gap: 0.75rem;
  padding: 0 0.875rem 0 0.375rem;
  border-color: var(--bf-door-card-edge, var(--bf-button-line));
  background-color: color-mix(in srgb, var(--bf-surface-bg) var(--bf-door-pill-veil), transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  box-shadow: var(--bf-door-card-bevel, none);
  color: var(--bf-surface-fg);
}

.bf-door nav.external .bf-idp:hover {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-door nav.external .bf-idp:focus-visible {
  outline: 2px solid var(--bf-cta-edge);
  outline-offset: 2px;
}

/* La marca del proveedor, en su baldosa. */
.bf-door nav.external .bf-idp > .mark {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: calc(var(--bf-button-radius) - 2px);
  background: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  color: var(--bf-surface-fg);
}

.bf-door nav.external .bf-idp > .mark > svg {
  width: 1.125rem;
  height: 1.125rem;
}

.bf-door nav.external .bf-idp > .nm {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La flecha de entrar: apagada en reposo; al apuntar sube de tinta y se adelanta tres píxeles, como la del CTA. */
.bf-door nav.external .bf-idp > .go {
  display: grid;
  place-items: center;
  flex: none;
  color: var(--bf-muted);
  transition:
    transform var(--bf-dur-side) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-door nav.external .bf-idp > .go > svg {
  width: 0.875rem;
  height: 0.875rem;
}

.bf-door nav.external .bf-idp:is(:hover, :focus-visible) > .go {
  color: var(--bf-surface-fg);
  transform: translateX(3px);
}

@media (prefers-reduced-motion: reduce) {
  .bf-door nav.external .bf-idp:is(:hover, :focus-visible) > .go {
    transform: none;
  }
}

:root[data-reduce~="motion"] .bf-door nav.external .bf-idp:is(:hover, :focus-visible) > .go {
  transform: none;
}

@media (prefers-reduced-transparency: reduce) {
  .bf-door nav.external .bf-idp {
    background-color: var(--bf-button-bg);
  }
}

:root[data-reduce~="transparency"] .bf-door nav.external .bf-idp {
  background-color: var(--bf-button-bg);
}

/* ═══ INSTALACIÓN Y CATÁLOGO, TRABAJADOS (chore 90.739) ═══════════════════════════════════════════════════════════
   Los módulos de las secciones Catálogo (Productos y su ficha, Catálogo de permisos) e Instalación (Licencias,
   Auditoría y Emitir avisos) con el vestido de los módulos trabajados —el catálogo de instancias, su ficha (90.716,
   `.bf-iglass`) y Usuarios (90.735)—: el CRISTAL FINO en bandas, tablas y tarjetas, sin caída en reposo (90.717) y con
   el canto de siempre; cabeceras con su entradilla; y, en Productos, las CARPETAS de Instancias y el LOGO de marca en
   cada tarjeta. Cada pantalla cuelga de su propia clase, así que nada de esto alcanza a otra. Sin palabra en el acento
   ni luz de cabecera (sólo el Inicio las lleva). */

/* ── Las cabeceras: el título y, debajo, su para qué en voz baja ── */
.bf-page-header > .ttl {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.bf-page-header > .ttl > h1 {
  margin: 0;
}

.bf-page-header > .ttl > .lead {
  max-width: 72ch;
  margin: 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

/* ── PRODUCTOS · la tarjeta: el logo de marca en su vitrina ──
   El logo es la pieza que dice qué producto es, así que va GRANDE y en el centro de la vista, como la baldosa de la
   instancia (90.680), sobre un vidrio neutro —un logo trae sus propios colores y el tono del producto debajo los
   ensuciaría—. Sin logo, la baldosa de siempre al mismo tamaño, con el icono o las iniciales. */
.bf-prodcard .tile {
  width: 3.75rem;
  height: 3.75rem;
  border-radius: 1.1rem;
  font-size: var(--bf-text-lead, 1.125rem);
}

.bf-prodcard .tile svg {
  width: 1.85rem;
  height: 1.85rem;
}

.bf-prodcard .tile.logo,
.bf-htile.logo {
  padding: 0.5rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 10%, transparent);
  overflow: hidden;
}

.bf-prodcard .tile.logo {
  width: 4.5rem;
  height: 4.5rem;
  padding: 0.625rem;
  border-radius: 1.25rem;
}

.bf-prodcard .tile.logo > img,
.bf-htile.logo > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

/* En la fila de la tabla la baldosa del logo es la de siempre, con menos relleno para que el logo se lea. */
.bf-htable .bf-htile.logo,
td.nm > .bf-htile.logo {
  padding: 0.25rem;
}

/* La vista de una tarjeta con logo, sin el halo del tono: el logo trae su color. Queda una luz blanca muy baja. */
.bf-prodcard.has-logo > .pv::before {
  background: radial-gradient(70% 70% at 50% 44%, color-mix(in srgb, var(--bf-surface-fg) 5%, transparent), transparent 72%);
}

/* La carpeta en la banda, cuando se miran «Todos»: su glifo y el nombre, en voz baja y recortable. */
.bf-prodcard .band > .fold {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  min-width: 0;
  margin: 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-mini);
}

.bf-prodcard .band > .fold > svg {
  flex: none;
  width: 0.8rem;
  height: 0.8rem;
  color: var(--bf-dim);
}

.bf-prodcard .band > .fold > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── PRODUCTOS · la carpeta nueva sin JavaScript (F-10): una tarjeta de cristal encima de la rejilla ── */
.bf-prodfolder-new {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
  padding: 1rem 1.125rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: var(--bf-radius-modal);
}

.bf-prodfolder-new > h2 {
  margin: 0;
  font-size: var(--bf-text-head);
}

.bf-prodfolder-new > p {
  margin: 0;
}

.bf-prodfolder-new > .acts {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* El carril de sección con un glifo en vez de unas letras. */
.bf-frail .fic > svg {
  width: 1rem;
  height: 1rem;
}

/* La cabecera del registro de un producto: el logo, un punto más grande que la baldosa de la instancia. */
.bf-prodrec .bf-htile.logo {
  position: relative;
  overflow: visible;
  padding: 0.45rem;
}

.bf-prodrec .bf-htile.logo > img {
  border-radius: 0.6rem;
}

.bf-prodrec .bf-rechead .sub {
  margin: 0.125rem 0 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
}

/* ── EL MATERIAL DE TRABAJO: el cristal fino de Usuarios (90.735) en las bandas y tarjetas de estas pantallas ──
   `.bf-work` envuelve lo que cada pantalla quiere en cristal (Catálogo de permisos, Auditoría, Emitir avisos); en la
   ficha de un producto lo hace la propia cabecera de cristal, para lo que viene detrás (pestañas Roles y Menú). */
.bf-work .bf-card,
.bf-work .bf-toolbar:has(> .bf-filterbox),
.bf-irec.bf-iglass ~ .bf-card,
.bf-irec.bf-iglass ~ .bf-toolbar:has(> .bf-filterbox) {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-work .bf-card,
  .bf-work .bf-toolbar:has(> .bf-filterbox),
  .bf-irec.bf-iglass ~ .bf-card,
  .bf-irec.bf-iglass ~ .bf-toolbar:has(> .bf-filterbox) {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-work .bf-card, .bf-work .bf-toolbar:has(> .bf-filterbox), .bf-irec.bf-iglass ~ .bf-card, .bf-irec.bf-iglass ~ .bf-toolbar:has(> .bf-filterbox)) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-work .bf-card,
  .bf-work .bf-toolbar:has(> .bf-filterbox),
  .bf-irec.bf-iglass ~ .bf-card {
    border-color: CanvasText;
  }
}

/* Las cabeceras de tarjeta con su glifo delante, en la tinta tenue, y una muesca para la cuenta. */
.bf-work .bf-card > header > h3 > svg,
.bf-iglass > header > h3 > svg {
  width: 1rem;
  height: 1rem;
  margin-right: 0.5rem;
  vertical-align: -0.15em;
  color: var(--bf-dim);
}

.bf-iglass > header > h3 > .n {
  margin-left: 0.5rem;
  padding: 0 0.4375rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  font-size: var(--bf-text-mini);
  font-weight: 600;
  color: var(--bf-muted);
  vertical-align: 0.1em;
}

/* ── CATÁLOGO DE PERMISOS · el selector de producto: una tira de pastillas con su logo ──
   Envuelve en vez de rodar: son pocas y se tienen que ver todas. La elegida, con el velo de seleccionado y la tinta de
   marca (M-9: «esto está elegido», no un relleno naranja). */
.bf-prodstrip {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin: 0 0 0.875rem;
}

.bf-prodstrip > a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  max-width: 100%;
  height: 2.125rem;
  padding: 0 0.75rem 0 0.25rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: 999px;
  background-color: var(--bf-lg-fill);
  color: var(--bf-muted);
  font-size: var(--bf-text-ui);
  text-decoration: none;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out),
    border-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-prodstrip > a:hover {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-prodstrip > a.on {
  border-color: color-mix(in srgb, var(--bf-accent) 32%, var(--bf-lg-line));
  background-color: var(--bf-selected);
  color: var(--bf-selected-ink);
  font-weight: 600;
}

.bf-prodstrip > a:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-prodstrip .bf-htile {
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 999px;
  font-size: var(--bf-text-mini);
}

.bf-prodstrip .bf-htile svg {
  width: 0.875rem;
  height: 0.875rem;
}

.bf-prodstrip .bf-htile.logo {
  padding: 0.2rem;
}

.bf-prodstrip .nm {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── AUDITORÍA · las tres cifras en grande, y la ventana ──
   Una rejilla que envuelve sola (MF-2): cuatro en una fila con ancho, dos y dos en el teléfono. Cada baldosa es
   cristal fino con su glifo en una ficha; el estado (rechazos, excepciones) tiñe la ficha y la cifra, y lo dice el
   rótulo escrito (M-2). */
.bf-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
  gap: 0.75rem;
}

.bf-kpi {
  --kp-tone: var(--bf-muted);
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  grid-template-areas: "ic v" "ic k";
  align-items: center;
  column-gap: 0.75rem;
  min-width: 0;
  padding: 0.875rem 1rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: var(--bf-radius-modal);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
  color: var(--bf-surface-fg);
  text-decoration: none;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out), transform var(--bf-dur-base) var(--bf-ease-out);
}

a.bf-kpi:hover {
  background-color: var(--bf-card-veil-hover, var(--bf-hover));
  transform: translateY(-1px);
}

a.bf-kpi:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-kpi.bad {
  --kp-tone: var(--bf-danger);
}

.bf-kpi.warn {
  --kp-tone: var(--bf-warning);
}

.bf-kpi > .ic {
  grid-area: ic;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--kp-tone) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--kp-tone) 20%, transparent);
  color: color-mix(in srgb, var(--kp-tone) 80%, var(--bf-surface-fg));
}

.bf-kpi > .ic > svg {
  width: 1.15rem;
  height: 1.15rem;
}

.bf-kpi > .v {
  grid-area: v;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 1.5rem;
  font-weight: 650;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.bf-kpi.bad > .v,
.bf-kpi.warn > .v {
  color: color-mix(in srgb, var(--kp-tone) 78%, var(--bf-surface-fg));
}

.bf-kpi.win > .v {
  font-size: var(--bf-text-head);
}

/* El rótulo parte línea en vez de cortarse: a 375 px la rejilla va a dos columnas y «Accesos rechazados» no cabe en
   una. La cifra sí se queda en una línea. */
.bf-kpi > .k {
  grid-area: k;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-kpi,
  .bf-prodstrip > a {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-kpi, .bf-prodstrip > a) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-kpi,
  .bf-prodstrip > a {
    border-color: CanvasText;
  }
}

/* La rejilla del panorama, con el aire de las tarjetas trabajadas. */
.bf-audit .bf-overview {
  gap: 1rem;
}

/* ── LICENCIAS · la clave pública a lo ancho y, al lado, rotar y las jubiladas ──
   En una columna en el teléfono; desde el corte ancho (MF-3), dos: la pública manda y el costado acompaña. */
.bf-keygrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

@media (min-width: 1100px) {
  .bf-keygrid {
    grid-template-columns: minmax(0, 1.65fr) minmax(0, 1fr);
  }
}

.bf-keyside {
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

.bf-keygrid .bf-card {
  margin: 0;
}

/* La cabecera de la clave que firma: la baldosa del glifo en la tinta del estado. */
.bf-keyrec .bf-htile {
  --hc-tone: var(--bf-success);
}

/* El pie de la pública: la garantía con su escudo, en voz baja. */
.bf-keyfoot > p {
  margin: 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
}

.bf-keyfoot > p > svg {
  margin-right: 0.4rem;
  vertical-align: -0.15em;
}

.bf-keyfoot > p > svg,
.bf-keysafe > svg {
  flex: none;
  width: 0.95rem;
  height: 0.95rem;
  color: var(--bf-success);
}

/* Lo que hay que saber antes de rotar: tranquiliza, así que va en verde suave y no en una caja de aviso. */
.bf-keysafe {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  margin: 0;
  padding: 0.625rem 0.75rem;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--bf-success) 8%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-success) 16%, transparent);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

.bf-keysafe > svg {
  margin-top: 0.15rem;
}

.bf-keyrotate .in > p:first-child {
  margin: 0;
}

/* Las jubiladas, en renglones: la clave en monoespaciada y, debajo, de cuándo a cuándo firmó. */
.bf-keylist {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0 0 0.375rem;
  list-style: none;
}

.bf-keylist > li {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
  padding: 0.5rem 1.125rem;
  border-top: 1px solid var(--bf-lg-line);
}

.bf-keylist > li > code {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--bf-text-label);
}

.bf-keylist > li > .when {
  color: var(--bf-muted);
  font-size: var(--bf-text-mini);
}

/* ═══ fin del bloque de la chore 90.739 ═══ */

/* ═══ EL CATÁLOGO DE INSTANCIAS, TRABAJADO (chore 90.745) ═════════════════════════════════════════════════════════════
   La pantalla de Instancias (/admin/instances) con el vestido de los módulos trabajados —Productos (90.739), Usuarios
   (90.735) y la ficha de instancia (90.716)—: el para qué de la pantalla pasa de renglón bajo el título a PISTA DEL
   TÍTULO (`details.bf-thint`, pieza común); la barra de filtros es una banda de CRISTAL FINO con el estado en
   pastillas con su cuenta; las tarjetas ganan la baldosa en el tono de su producto y el estado en una pastilla; y los
   vacíos dan el paso que los arregla. Sin caída en reposo, sin palabra en el acento ni luz de cabecera. Todo cuelga de
   `.bf-instcat` salvo la pista, que es del chasis y la puede llevar cualquier título. */

/* ── LA PISTA DEL TÍTULO: el «i» pegado al título y, al abrirlo, su para qué en una pieza de cristal ──
   Es un <details>: sin JavaScript se abre pulsándolo; ts/ui/title-hint.ts añade el apuntado y el Escape. La pieza se
   cuelga del bloque del título (no de la «i»), así que nace a su canto izquierdo y nunca se sale por la derecha de
   un teléfono (MF-5). Lo que flota es cristal con su caída (M-4); nada en reposo. */
/* La cabecera con pista sube por encima del árbol y la rejilla, que van detrás en el documento y la taparían. */
.bf-page-header:has(> .ttl.bf-thead) {
  position: relative;
  z-index: 20;
}

.bf-page-header > .ttl.bf-thead {
  position: relative;
  flex-direction: row;
  align-items: center;
  gap: 0.375rem;
}

.bf-thint {
  flex: none;
}

.bf-thint > summary {
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 999px;
  color: var(--bf-dim);
  list-style: none;
  cursor: pointer;
  transition:
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-thint > summary::-webkit-details-marker {
  display: none;
}

.bf-thint > summary > svg {
  width: 1rem;
  height: 1rem;
}

.bf-thint > summary:hover,
.bf-thint[open] > summary {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-thint > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

.bf-thint > .pop {
  position: absolute;
  top: calc(100% + 0.5rem);
  left: 0;
  z-index: 30;
  width: max-content;
  max-width: min(26rem, calc(100vw - 2rem));
  padding: 0.75rem 0.875rem;
  /* Más densa que el menú «···»: es texto corrido, y pasa por encima de tarjetas con su propio texto. */
  background: color-mix(in srgb, var(--bf-card-bg) 90%, transparent);
  transform-origin: top left;
  animation: bf-thint-in var(--bf-dur-base) var(--bf-ease-out);
}

@keyframes bf-thint-in {
  from {
    opacity: 0;
    transform: translateY(-0.3rem) scale(0.97);
  }
}

.bf-thint > .pop > p {
  margin: 0;
  font-size: var(--bf-text-ui);
  font-weight: 400;
  line-height: 1.55;
  color: var(--bf-surface-fg);
}

.bf-thint > .pop > p.sub {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid color-mix(in srgb, var(--bf-surface-fg) 10%, transparent);
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

@media (prefers-reduced-motion: reduce) {
  .bf-thint > .pop {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-thint > .pop {
  animation: none;
}

@media (forced-colors: active) {
  .bf-thint > .pop {
    border: 1px solid CanvasText;
  }
}

/* ── LA BARRA: una banda de cristal fino, la de Usuarios, con el estado delante y el buscador al canto ── */
.bf-instcat .bf-ibar {
  gap: 0.5rem 0.625rem;
  margin: 0 0 1rem;
  padding: 0.4375rem 0.5rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: var(--bf-radius-modal);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-instcat .bf-ibar {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] .bf-instcat .bf-ibar {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-instcat .bf-ibar {
    border-color: CanvasText;
  }
}

.bf-instcat .bf-ibar > .cnt {
  font-variant-numeric: tabular-nums;
}

.bf-instcat .bf-ibar > .bf-fclear > svg {
  width: 0.8rem;
  height: 0.8rem;
}

/* EL ESTADO EN PASTILLAS, las de Usuarios, con su cuenta. El carril es su propio envoltorio de desplazamiento (MF-5):
   con los cinco estados no cabe en un teléfono, y antes que envolver en dos renglones, rueda. */
.bf-istates {
  max-width: 100%;
}

.bf-istates > .row {
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}

.bf-istates > .row::-webkit-scrollbar {
  display: none;
}

.bf-istates label {
  flex: none;
  padding: 0 0.5rem 0 0.7rem;
}

.bf-istates label.warn > i {
  background-color: var(--bf-warning);
}

.bf-istates label.bad > i {
  background-color: var(--bf-danger);
}

.bf-istates label.off > i {
  background-color: var(--bf-off);
}

/* «Sin sondear» no es un color: es un hueco, un aro sin relleno. */
.bf-istates label.unk > i {
  background: transparent;
  box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--bf-muted) 70%, transparent);
}

/* «Todas»: el punto partido entre vivo y caído, como el «Todos» de Usuarios. */
.bf-istates label.all > i {
  background: linear-gradient(90deg, var(--bf-success) 50%, var(--bf-danger) 50%);
}

.bf-istates label > .n {
  display: inline-grid;
  place-items: center;
  min-width: 1.25rem;
  height: 1.05rem;
  margin-left: 0.125rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-weight: 600;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.bf-istates label:has(input:checked) > .n {
  background-color: color-mix(in srgb, var(--bf-selected-ink) 14%, transparent);
  color: var(--bf-selected-ink);
}

/* ── LAS TARJETAS: la baldosa en el tono de SU PRODUCTO y el estado en una pastilla ──
   El estado sigue diciéndose por tres vías —halo, luz y texto (M-2)—; la baldosa deja de ser gris y dice de qué
   producto es con su color de siempre (el de su tarjeta en Productos), con un aro fino alrededor en vez de sombra. */
.bf-instcat .bf-inst.bf-hcard .tile {
  background: color-mix(in srgb, var(--pt, var(--bf-muted)) 16%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--pt, var(--bf-muted)) 26%, transparent);
  color: color-mix(in srgb, var(--pt, var(--bf-muted)) 74%, var(--bf-surface-fg));
  outline: 1px solid color-mix(in srgb, var(--pt, var(--bf-muted)) 12%, transparent);
  outline-offset: 0.45rem;
}

.bf-instcat .bf-inst.bf-hcard > .pv > .state {
  top: 0.5rem;
  left: 0.5rem;
  padding: 0.1875rem 0.5rem 0.1875rem 0.4375rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) 66%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hc-tone) 24%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.bf-instcat .bf-inst.bf-hcard > .pv > .state > [data-health-since]:empty {
  display: none;
}

/* ── LOS VACÍOS: en su pieza de cristal, a buen alto, con su salida debajo ── */
.bf-instempty > .bf-empty {
  min-height: 18rem;
  border: 1px solid var(--bf-lg-line);
  border-radius: var(--bf-radius-modal);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

.bf-instempty > .bf-empty > .bf-btn > svg {
  width: 0.9rem;
  height: 0.9rem;
}

@media (prefers-reduced-transparency: reduce) {
  .bf-instempty > .bf-empty {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] .bf-instempty > .bf-empty {
  background-color: var(--bf-card-bg);
}

/* ═══ fin del bloque de la chore 90.745 ═══ */

/* ═══ LO QUE SE ABRE, SE CIERRA COMO SE ABRIÓ (chore 90.772) ═══════════════════
   Petición del usuario: un modal o un desplegable desaparece con el mismo fundido con el que aparece, no de un
   fotograma para otro. La ENTRADA sigue siendo la animación de cada pieza (bf-modal-in, bf-fade-in, bf-pop-in); la
   SALIDA es una transición hacia ese mismo punto de partida, en el mismo tiempo; con la curva simétrica (--bf-ease-side),
   porque la de salida, al revés, la dejaba casi invisible en el primer tercio y se leía como un corte.

   · Capas con `hidden` (.bf-overlay): el `display` entra en la transición con allow-discrete, así que la capa aguanta
     pintada lo que dura el fundido aunque `[hidden]` diga `display:none !important` (las transiciones mandan sobre
     cualquier declaración). Al revés no hay transición: oculta no tiene estilo previo, y entra su animación.
     Las capas que se construyen en el momento (confirm.ts, panel-modal.ts) se ocultan primero y se quitan después.
   · Desplegables <details class="bf-more">: el contenido de un <details> cerrado deja de pintarse por
     `content-visibility`, que también entra en la transición; el panel funde y vuelve a su esquina.
   Con el movimiento apagado, --bf-dur-base vale 1 ms y todo se va en el acto. */
.bf-overlay {
  transition: display var(--bf-dur-base) allow-discrete;
}

.bf-overlay[hidden] {
  pointer-events: none;
}

.bf-overlay .bf-scrim,
.bf-overlay .bf-modal {
  transition:
    opacity var(--bf-dur-base) var(--bf-ease-side),
    transform var(--bf-dur-base) var(--bf-ease-side);
}

.bf-overlay[hidden] .bf-scrim {
  opacity: 0;
}

.bf-overlay[hidden] .bf-modal {
  opacity: 0;
  transform: translateY(0.75rem) scale(0.98);
}

.bf-more::details-content {
  transition: content-visibility var(--bf-dur-base) allow-discrete;
}

.bf-more .pop {
  transition:
    opacity var(--bf-dur-base) var(--bf-ease-side),
    transform var(--bf-dur-base) var(--bf-ease-side);
}

.bf-more:not([open]) .pop {
  opacity: 0;
  transform: translate(0.15rem, 0.4rem) scale(0.96);
  pointer-events: none;
}

/* Los que abren hacia abajo (bf-pop-down) vuelven hacia arriba. */
.bf-hcard > .bf-more:not([open]) .pop,
.bf-htable td.acts .bf-more:not([open]) .pop {
  transform: translate(0.15rem, -0.4rem) scale(0.96);
}

/* ═══ fin del bloque de la chore 90.772 ═══ */

/* ═══ EL FOCO DE UN BUSCADOR ES EL DE LA CAJA ENTERA (chore 90.777) ═══════════════
   Petición del usuario: el foco del buscador de carpetas —y de cualquier buscador— abarca la caja entera, lupa y aspa
   incluidas, y es EL MISMO que el del resto de campos (M-6: el canto se tiñe de --bf-field-focus y engorda a dos
   píxeles). Hasta aquí el anillo global de foco caía en el <input>, que empieza después de la lupa. Y desde la 90.793,
   sin el velo propio del foco: el fondo no cambia al enfocar, como en el resto de campos. */
.bf-fsearch > input:focus,
.bf-fsearch > input:focus-visible,
.bf-field-search > input:focus,
.bf-field-search > input:focus-visible {
  outline: none;
  box-shadow: none;
}

.bf-fsearch:focus-within,
.bf-field-search:focus-within {
  border-color: var(--bf-field-focus);
  box-shadow: inset 0 0 0 1px var(--bf-field-focus);
  /* Chore 90.793 (petición del usuario): el foco NO cambia el fondo; el buscador se queda con el de los campos. */
  background-color: var(--bf-field-bg);
}

/* ═══ fin del bloque de la chore 90.777 ═══ */

/* ═══ LA SOMBRA QUE SIGUE AL GIRO (chore 90.774) ══════════════════════════════════
   Petición del usuario: al inclinarse una tarjeta de cristal, una sombra DISIMULADA debajo que se desplaza al lado
   contrario del puntero, para que la inclinación se lea en 3D. En reposo sigue sin sombra (90.636, 90.639): sólo se
   enciende al apuntar, y se apaga con el mismo fundido. Es la capa `::before` del relieve, que ya se movía con
   `--dx`/`--dy` y giraba con `--tilt`; la tinta es la del suelo de cada tema (`--floor`). */
.bf-home-card[data-relief]::before,
.bf-hcard[data-relief]::before {
  box-shadow:
    0 2px 6px -3px color-mix(in srgb, var(--floor) 12%, transparent),
    0 16px 30px -14px color-mix(in srgb, var(--floor) calc(var(--drift-shade, 0.3) * 100%), transparent);
  translate: calc(var(--dx, 0) * -7px) calc(var(--dy, 0) * -5px + 5px);
  opacity: 0;
}

.bf-home-card[data-relief]:hover::before,
.bf-hcard[data-relief]:hover::before {
  opacity: 1;
}

/* En oscuro, la tinta negra sobre el lienzo oscuro se pierde: más densa para decir lo mismo. */
:root[data-theme="dark"] :is(.bf-home-card, .bf-hcard)[data-relief] { --drift-shade: 0.6; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-home-card, .bf-hcard)[data-relief] { --drift-shade: 0.6; }
}

/* ═══ fin del bloque de la chore 90.774 ═══ */

/* ═══ LA GOTA DE LOS CONMUTADORES SEGMENTADOS (chores 90.771 y 90.786) ════════════════
   Petición del usuario, elegida entre los mockups de la gota (90.771) y extendida a TODOS los segmentados (90.786):
     · los conmutadores de vista de la cabecera —«Tarjetas | Tabla» de Instancias y Productos (`.bf-tabs` dentro de
       `.bf-page-header .acts`)—,
     · las pestañas que conmutan en la página (`.bf-tabs[role="group"]`: los modos, la vista previa y el tipo de ruta
       del menú de una instancia),
     · todos los `.bf-seg` —de enlaces («Árbol | Tabla», «Personas · Matriz · Lista», la ventana de Actividad, el modo
       del alta de persona y de rol), de radios (destinatario de un aviso, modo de la importación) y de botones (la
       velocidad del visor)—, también el compacto del centro de control en Ajustes (`.bf-seg-cc`),
     · y el carril de estados en pastillas de los filtros (`.bf-fchips > .row`: «Todas | Sin sondear» de Instancias,
       el estado de Personas).
   Fuera, a sabiendas, las pestañas de las FICHAS (`.bf-tabs` fuera de la cabecera): un carril de navegación con
   contadores que se desborda en el teléfono, no un mando de pocas posiciones. Sí comparten el motor del viaje.
   El carril es cristal con el filete del buscador global; la pastilla es una GOTA casi invisible (90.780: su canto es
   sólo ese filete, con un brillo interior de borde, la sombra de contacto y una brasa del acento debajo, todo tenue).
   La mueve ts/ui/tabs.ts (no hay transición CSS: la anima el script con la duración y la curva de aquí):
     · el viaje, un deslizamiento suave de ~0,4 s (`--gota-spring`; chore 90.960, sin estirarse ni rebotar);
     · los rótulos se cruzan en fundido: el de destino se aclara mientras el de origen se apaga;
     · y el de destino se amplía un 5 % cuando ella se asienta (el carril lleva `data-glide` mientras viaja).
   La opción que se pinta como puesta es la que lleva `data-gota-on` (la marca el script en el mismo toque, antes de
   que llegue la página nueva); sin script, el marcado de siempre (`.on`, `aria-current`, el radio marcado) con su
   fondo. Mismo peso de letra en todas las opciones: cambiarlo movería los anchos a mitad del viaje.
   El acento sólo como luz; ningún color por opción. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  /* La luz de la gota en claro: su eco suave y la sombra cálida del suelo de la casa. */
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 28%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-tint-warm) 14%, transparent);
  --gota-lift: 1.06;
  /* Un deslizamiento SUAVE (chore 90.960, petición del usuario: «no debe hacer el bounce, debe ser suave»): arranca y
     llega con calma, sin salir de golpe ni pasarse, y la gota ya no se estira por el camino (ts/ui/tabs.ts). Antes era
     un muelle crítico muestreado a linear() que salía firme, y el estirón con su vuelta se leía como un rebote. */
  --gota-glide: 420ms;
  --gota-spring: cubic-bezier(0.45, 0, 0.25, 1);
  position: relative;
  flex-wrap: nowrap;
  gap: 2px;
  padding: 2px;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-head-glass) 45%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

:root[data-theme="dark"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 40%, transparent);
  --gota-lift: 1.3;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
    --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 40%, transparent);
    --gota-lift: 1.3;
  }
}

/* La altura de control (1,75 rem) en los de tamaño normal; el de estados ya la tiene por sus etiquetas y el del centro
   de control es compacto a propósito (calcado del shell). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc)) {
  height: 1.75rem;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc)) > :is(a, button, label, i) {
  height: 100%;
  padding-block: 0;
}

/* Las opciones, por delante de la gota, con el fundido cruzado de la tinta y la ampliación suave. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > :is(a, button, label, i) {
  position: relative;
  z-index: 1;
  transition:
    color 320ms var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out),
    transform 460ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > :is(a, button, label, i) > svg {
  transition: color 320ms var(--bf-ease-out);
}

/* CON GOTA, lo puesto lo dice ella: ningún fondo ni sombra en las opciones, la misma letra en todas, la tinta apagada
   en las demás y la del texto en la puesta (`data-gota-on`), y el apuntado sólo aclara la tinta. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink)
  > :is(a, button, label, i) {
  background-color: transparent !important;
  box-shadow: none !important;
  animation: none;
  color: var(--bf-muted);
  font-weight: 500;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink)
  > :is(a, button, label):hover {
  color: var(--bf-surface-fg);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink)
  > [data-gota-on] {
  color: var(--bf-surface-fg);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink)
  > :not([data-gota-on]) > svg {
  color: inherit;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink)
  > [data-gota-on] > svg {
  color: var(--bf-selected-ink);
}

/* LA AMPLIACIÓN: el rótulo puesto crece un 5 %, y no mientras la gota viaja (`data-glide`): entra al asentarse. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink):not([data-glide])
  > [data-gota-on] {
  transform: scale(1.05);
}

/* LA GOTA. Casi transparente: un toque de saturación y de luz sobre lo que tiene detrás, un brillo especular tenue
   arriba a la izquierda, el borde que se espesa hacia el canto, el brillo interior, la sombra de contacto y la brasa.
   Sin transición: el viaje lo anima el script (una transición aquí se impondría a su animación). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  top: 2px;
  bottom: 2px;
  left: 0;
  z-index: 0;
  border-radius: 999px;
  background:
    radial-gradient(38% 70% at 26% 6%, var(--gota-spec-soft), transparent 70%),
    radial-gradient(120% 140% at 50% 50%, transparent 55%, color-mix(in srgb, var(--bf-card-bg) 40%, transparent));
  -webkit-backdrop-filter: blur(0.6px) saturate(2) brightness(var(--gota-lift));
  backdrop-filter: blur(0.6px) saturate(2) brightness(var(--gota-lift));
  box-shadow:
    inset 0 0 3px 0 var(--gota-spec-soft),
    0 3px 5px -3px var(--gota-drop),
    0 4px 8px -6px color-mix(in srgb, var(--bf-accent) 22%, transparent);
  transition: none;
}

/* El canto: 1 px del filete del buscador, un anillo por máscara (90.780: sin arco blanco, sin tibio, sin irisación). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background: linear-gradient(var(--bf-edge-line), var(--bf-edge-line));
  /* El #fff de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  mask-composite: exclude;
}

/* Las pestañas de las fichas comparten el motor: también las mueve el script, con su duración y su curva de siempre
   (--bf-dur-glide, --bf-ease-out). Su transición CSS se quita para que no pise la animación. */
.bf-tabs-ink {
  transition: none;
}

/* MOVIMIENTO REDUCIDO, por las dos vías: la gota se pone en su sitio sin viaje, sin muelle y sin estirarse (el script
   lee la duración de aquí), y el rótulo no se amplía. */
@media (prefers-reduced-motion: reduce) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) { --gota-glide: 0ms; }
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > * { transform: none !important; }
}

:root[data-reduce~="motion"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-glide: 0ms;
}

:root[data-reduce~="motion"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > * {
  transform: none !important;
}

/* TRANSPARENCIA REDUCIDA, por las tres vías de tokens.css: sin desenfoque; la gota pasa a la tarjeta maciza, con su
   canto y su sombra, para que lo que pisa se siga leyendo. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
    background: var(--bf-card-bg);
  }
}

@media (prefers-reduced-transparency: reduce) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: var(--bf-card-bg);
}

/* ═══ fin del bloque de las chores 90.771 y 90.786 ═══ */

/* ═══ LA TABLA, CON EL FONDO DE LAS DE HUGIN (chore 90.781) ═══════════════════════
   Petición del usuario: la tabla de instancias (y con ella las demás `.bf-htable`) lleva el fondo de las tablas de
   Hugin, tal cual su manage.css: el `--panel-color` de Hugin (#2C3239 de noche, #F6F6F6 de día) al 24 % en oscuro y
   al 60 % en claro, y la cabecera esmerilada 28 puntos más densa. Sustituye el velo neutro y cálido de la 90.731
   SÓLO en el fondo y la cabecera; el pelo y el realce de fila se quedan como estaban. */
.bf-card.bf-htable {
  --ht-panel: var(--bf-panel-color);
  --ht-veil: 60%;
  --bf-card-veil: color-mix(in srgb, var(--ht-panel) var(--ht-veil), transparent);
  --bf-card-veil-hover: var(--bf-card-veil);
  --ht-head: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) + 28%), transparent);
  background-color: var(--bf-card-veil);
}

:root[data-theme="dark"] .bf-card.bf-htable {
  --ht-panel: var(--bf-panel-color);
  --ht-veil: 24%;
  --bf-card-veil: color-mix(in srgb, var(--ht-panel) var(--ht-veil), transparent);
  --ht-head: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) + 28%), transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-card.bf-htable {
    --ht-panel: var(--bf-panel-color);
    --ht-veil: 24%;
    --bf-card-veil: color-mix(in srgb, var(--ht-panel) var(--ht-veil), transparent);
    --ht-head: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) + 28%), transparent);
  }
}

/* ═══ fin del bloque de la chore 90.781 ═══ */

/* ═══ LA TABLA DE USUARIOS, AL ALTO DE LA VISTA (chore 90.788) ══════════════════════
   Petición del usuario: la tabla de usuarios ocupa sólo el alto de la vista y rueda por dentro. Es la receta de la
   90.573 (`.bf-htable[data-fit="view"]`) para una tabla que no es de la variante de Hugin: la tarjeta, columna de flex
   con la altura que mide fillToView; el envoltorio se queda con el resto y rueda con la cabecera pegada. En teléfono no
   aplica (MF): rueda la página. */
@media (min-width: 620px) {
  .bf-card:not(.bf-htable)[data-fill][data-fit="view"] {
    display: flex;
    flex-direction: column;
    height: var(--fill-h, auto);
    min-height: 0;
  }

  .bf-card:not(.bf-htable)[data-fill][data-fit="view"] > .bf-table-wrap {
    flex: 1 1 auto;
    min-height: 0;
    max-height: none;
    overflow: auto;
  }

  .bf-card:not(.bf-htable)[data-fit="view"] .bf-table thead th {
    position: sticky;
    top: 0;
    z-index: 1;
  }
}

/* ═══ fin del bloque de la chore 90.788 ═══ */

/* ═══ EL MODAL CON CARRIL NO CAMBIA DE ALTO, Y EL CARRIL SE MUEVE SUAVE (chores 90.789 y 90.790) ═══════════════
   Peticiones del usuario: al plegar o desplegar el carril de apartados el modal no crece, al cerrar un apartado no
   pierde altura, y lo que no es el carril se recoloca con más suavidad. El alto lo fija el script al abrir
   (panel-modal.ts, instance-new.ts); la columna conserva su techo de siempre y rueda dentro (.bf-menued-panes). El carril ensancha con la curva de salida de la casa en algo más de tiempo, y los nombres entran
   con fundido en vez de destaparse de golpe. Movimiento reducido: seco, como ya estaba. */
.bf-menued-pnav {
  transition: width 420ms var(--bf-ease-out);
}

.bf-menued-pnav .nm {
  opacity: 0;
  transition:
    max-width 420ms var(--bf-ease-out),
    opacity 260ms var(--bf-ease-out);
}

.bf-menued-pnav[data-open="true"] .nm {
  opacity: 1;
  transition:
    max-width 420ms var(--bf-ease-out),
    opacity 300ms var(--bf-ease-out) 120ms;
}

@media (prefers-reduced-motion: reduce) {
  .bf-menued-pnav,
  .bf-menued-pnav .nm {
    transition: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-menued-pnav, .bf-menued-pnav .nm) {
  transition: none;
}

/* ═══ fin del bloque de las chores 90.789 y 90.790 ═══ */

/* ═══ LA FICHA DE INSTANCIA, COLUMNA DE IDENTIDAD (chore 90.787) ═══════════════════════════════════════════════════════
   La variante B del mockup, elegida por el usuario y a TODO EL ANCHO DE LA VISTA: la columna de identidad a la izquierda
   (`.bf-iside`: baldosa neutra con su LED, estado, acciones del registro, la navegación de la ficha en vertical y los
   hechos del despliegue) y la pestaña llenando el resto hasta el borde (`.bf-ipane`). Licencia y Salud se componen en
   MOSAICO (`.bf-ibento`, seis columnas por contenedor) y Datos reagrupa el formulario de siempre en tarjetas con su
   cabecera, sin tocar su marcado (lo comparte con el alta). El material es el cristal de las tarjetas: el velo y un
   filete de 1 px, sin sombras en reposo; el naranja sólo en la llamada y en la pestaña puesta. Todo cuelga de `.bf-ific`,
   así que no toca la pestaña Menú ni el alta. Por debajo de 900 px la columna pasa arriba; a ancho de teléfono, una. */

.bf-ific {
  --bf-isteel: 70 130 180;
  display: grid;
  grid-template-columns: minmax(15.5rem, 18.5rem) minmax(0, 1fr);
  gap: 1.25rem;
  align-items: start;
  width: 100%;
}

:root[data-theme="dark"] .bf-ific {
  --bf-isteel: 90 150 210;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-ific {
    --bf-isteel: 90 150 210;
  }
}

/* El material común de la columna y de las celdas: el velo de las tarjetas con su filete, sin caída. Las que llevan
   `.bf-iglass` ya traen el filete por contorno (los cantos, 90.764); el grupo del formulario lo toma igual. */
.bf-ific :is(.bf-iside, .bf-icell),
.bf-ific .bf-ipane .bf-form-row {
  border-radius: var(--bf-radius-lg);
  background-color: var(--bf-card-veil);
  box-shadow: none;
}

.bf-ific .bf-ipane .bf-form-row {
  border: 1px solid transparent;
  outline: 1px solid var(--bf-edge-line);
  outline-offset: -1px;
}

/* ── La columna de identidad ── */
.bf-iside {
  position: sticky;
  top: 1rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
  padding: 1.125rem;
}

.bf-iside-id {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  min-width: 0;
}

/* La baldosa NEUTRA: sin tinte de producto; el estado lo dice su LED en la esquina (y el texto de al lado, M-2). */
.bf-itile {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 3.5rem;
  height: 3.5rem;
  border-radius: 1rem;
  background: color-mix(in srgb, var(--bf-muted) 11%, var(--bf-card-veil));
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  color: var(--bf-muted);
}

.bf-itile > svg {
  width: 1.5rem;
  height: 1.5rem;
}

.bf-itile > b {
  font-size: var(--bf-text-lead);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.bf-itile > .bf-led {
  position: absolute;
  right: -3px;
  bottom: -3px;
  width: 0.75rem;
  height: 0.75rem;
  background: currentColor;
  box-shadow: 0 0 0 3px var(--bf-card-bg);
}

.bf-iside-id .who {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.bf-iside-id .lbl {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-iside-id .who > b {
  font-size: var(--bf-text-head);
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.bf-iside-id .since {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  margin-top: 0.125rem;
}

.bf-iside-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
}

.bf-iside-acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.bf-iside-acts > form {
  display: contents;
}

.bf-iside-acts .bf-btn.open {
  flex: 1 1 auto;
  justify-content: center;
}

.bf-iside.dim .bf-itile {
  opacity: 0.6;
}

/* La navegación de la ficha: el carril `.bf-tabs` de siempre (enlaces, capa sin recarga), en VERTICAL y sin gota; la
   puesta lleva el fondo del velo cerrado y su filete, y la tinta de marca en el glifo. */
.bf-ific .bf-ivtabs {
  flex-direction: column;
  align-self: stretch;
  width: auto;
  max-width: none;
  gap: 0.125rem;
  margin: 0 -0.375rem;
  padding: 0.75rem 0 0;
  border-top: 1px solid var(--bf-edge-line);
  border-radius: 0;
  background: none;
  overflow: visible;
}

.bf-ific .bf-ivtabs > .bf-tabs-ink {
  display: none;
}

.bf-ific .bf-ivtabs > a {
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--bf-radius-btn);
  font-size: var(--bf-text-body);
}

.bf-ific .bf-ivtabs > a .t {
  flex: 1 1 auto;
}

.bf-ific .bf-ivtabs > a .tail {
  display: inline-flex;
  align-items: center;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-ific .bf-ivtabs > a:is(.on, [aria-current="page"]) {
  background-color: var(--bf-card-veil-hover);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-iside-facts {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin: 0;
  padding: 0.75rem 0 0;
  border-top: 1px solid var(--bf-edge-line);
  list-style: none;
  font-size: var(--bf-text-ui);
}

.bf-iside-facts > li {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  min-width: 0;
}

.bf-iside-facts > li > svg {
  flex: none;
  width: 0.9375rem;
  height: 0.9375rem;
  color: var(--bf-muted);
}

.bf-iside-facts > li > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-iside-facts > li.mono > span {
  font-family: var(--bf-font-mono);
  font-size: 12px;
}

/* ── La pestaña ── */
.bf-ipane {
  container: bf-ipane / inline-size;
  display: flex;
  flex-direction: column;
  gap: 1rem;
  min-width: 0;
}

.bf-ipane-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  min-height: 2rem;
}

.bf-ipane-head > h2 {
  flex: 1 1 auto;
  margin: 0;
  font-size: var(--bf-text-head);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.bf-ipane-head > .acts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.bf-ibento {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1rem;
}

.bf-ibento > .w2 { grid-column: span 2; }
.bf-ibento > .w3 { grid-column: span 3; }
.bf-ibento > .w4 { grid-column: span 4; }
.bf-ibento > .w6 { grid-column: 1 / -1; }

.bf-icell {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
  min-width: 0;
  padding: 1.125rem 1.25rem;
}

.bf-icell-k,
.bf-icell-h {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: var(--bf-text-label);
  font-weight: 500;
  color: var(--bf-muted);
}

.bf-icell-h {
  font-size: var(--bf-text-body);
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-icell-k > svg,
.bf-icell-h > svg {
  flex: none;
  width: 0.9375rem;
  height: 0.9375rem;
  color: var(--bf-muted);
}

.bf-icell-h .n {
  font-size: var(--bf-text-label);
  font-weight: 500;
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-icell-h > .bf-pill {
  margin-left: auto;
}

.bf-icell .big {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 0;
  font-size: 1.5rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

.bf-icell .big > b {
  font-weight: 600;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-icell .big.sm {
  font-size: 1.0625rem;
}

.bf-icell .big.off > b {
  color: var(--bf-muted);
  font-weight: 500;
}

.bf-icell .big > small {
  font-size: var(--bf-text-ui);
  font-weight: 500;
  letter-spacing: 0;
  color: var(--bf-muted);
}

.bf-icell .big > small em {
  font-style: normal;
  color: var(--bf-danger-text, var(--bf-danger));
}

.bf-icell .big > .bf-led {
  align-self: center;
  width: 0.75rem;
  height: 0.75rem;
}

.bf-icell .sub {
  margin: 0;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-icell .sub.wrap {
  white-space: normal;
}

.bf-icell .sub.mono {
  font-family: var(--bf-font-mono);
  font-size: 11.5px;
}

.bf-icell .bf-hint {
  margin: 0;
}

.bf-icell-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--bf-edge-line);
}

.bf-icell-foot > .bf-hint {
  flex: 1 1 16rem;
}

.bf-icell.stack {
  gap: 0.75rem;
}

/* ── Licencia: el anillo y la tira de meses ── */
.bf-ivig .ringrow {
  display: flex;
  align-items: center;
  gap: 1.25rem;
}

.bf-ivig .ring {
  flex: none;
  width: 7rem;
  height: 7rem;
}

.bf-ivig .ring .tr {
  stroke: color-mix(in srgb, var(--bf-muted) 14%, transparent);
}

.bf-ivig .ring .fg {
  stroke: rgb(var(--bf-isteel) / 0.9);
}

.bf-ivig.warn .ring .fg {
  stroke: var(--bf-warning);
}

.bf-ivig .ring text {
  fill: var(--bf-surface-fg);
  font-family: var(--bf-font-family);
}

.bf-ivig .ring .n {
  font-size: 24px;
  font-weight: 600;
}

.bf-ivig .ring .u {
  fill: var(--bf-muted);
  font-size: 10px;
}

.bf-ivig.bad .ring .u {
  fill: var(--bf-danger-text, var(--bf-danger));
  font-size: 12px;
  font-weight: 600;
}

.bf-ivig .txt {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.bf-ivig .txt .lbl {
  font-size: var(--bf-text-label);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-muted);
}

.bf-ivig .txt > b {
  font-size: var(--bf-text-head);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.bf-ivig .txt > p {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
}

.bf-ivig .txt > p.note {
  font-size: var(--bf-text-label);
}

.bf-imonths {
  display: grid;
  grid-template-columns: repeat(14, minmax(0, 1fr));
  gap: 0.25rem;
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-imonths > li {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3125rem;
  min-width: 0;
  font-size: 10.5px;
  color: var(--bf-muted);
}

.bf-imonths > li > i {
  width: 100%;
  height: 1.875rem;
  border-radius: 0.375rem;
  background: color-mix(in srgb, var(--bf-muted) 9%, transparent);
}

.bf-imonths > li.valid > i {
  background: color-mix(in srgb, var(--bf-success) 30%, transparent);
}

.bf-imonths > li.grace > i {
  background: repeating-linear-gradient(135deg, color-mix(in srgb, var(--bf-warning) 55%, transparent) 0 4px, transparent 4px 8px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-warning) 38%, transparent);
}

.bf-imonths > li.now > i {
  box-shadow: inset 0 0 0 2px var(--bf-surface-fg);
}

.bf-imonths > li.now > span {
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-imonths > li > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-ivig .key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-ivig .key > li {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.bf-ivig .key .sw {
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 3px;
}

.bf-ivig .key .sw.valid { background: color-mix(in srgb, var(--bf-success) 45%, transparent); }
.bf-ivig .key .sw.grace { background: repeating-linear-gradient(135deg, var(--bf-warning) 0 2px, transparent 2px 4px); }
.bf-ivig .key .sw.used { background: color-mix(in srgb, var(--bf-muted) 30%, transparent); }

/* Los hechos de la licencia, apilados con filete entre uno y otro. */
.bf-ifacts2 {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.bf-ifacts2 > div {
  display: flex;
  flex-direction: column;
  gap: 0.3125rem;
  padding: 0.6875rem 0;
}

.bf-ifacts2 > div + div {
  border-top: 1px solid var(--bf-edge-line);
}

.bf-ifacts2 > div:first-child { padding-top: 0; }
.bf-ifacts2 > div:last-child { padding-bottom: 0; }

.bf-ifacts2 dt {
  display: flex;
  align-items: center;
  gap: 0.4375rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-ifacts2 dt > svg {
  width: 0.875rem;
  height: 0.875rem;
}

.bf-ifacts2 dd {
  margin: 0;
  font-size: var(--bf-text-body);
  font-weight: 600;
}

/* Los topes: el nombre y el valor declarado, sin barra (Bifrost no conoce el consumo). */
.bf-ilimrows {
  display: flex;
  flex-direction: column;
  margin: 0;
}

.bf-ilimrows > div {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.5rem 0;
}

.bf-ilimrows > div + div {
  border-top: 1px solid var(--bf-edge-line);
}

.bf-ilimrows dd {
  margin: 0;
  font-size: 1.0625rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* La fijación de clave: la huella en bloques, desde cuándo y la válvula, en un renglón. */
.bf-ipin .pinrow {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem 2rem;
}

.bf-ipin .pinrow > div {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-width: 0;
}

.bf-ipin .pinrow .lbl {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-ipin .pinrow > form {
  margin-left: auto;
}

.bf-ifp {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3125rem;
  font-family: var(--bf-font-mono);
  font-size: 12.5px;
}

.bf-ifp > i {
  font-style: normal;
  padding: 0.1875rem 0.4375rem;
  border-radius: 0.4375rem;
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

/* ── Salud: la minigráfica, la tira de franjas y la línea de tiempo de las entregas ── */
.bf-ispark {
  display: block;
  width: 100%;
  height: 2.5rem;
}

.bf-ispark path {
  stroke: rgb(var(--bf-isteel));
  stroke-width: 1.75;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.bf-ispark circle {
  fill: rgb(var(--bf-isteel));
}

.bf-iuptime {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);
  gap: 0.25rem;
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-iuptime > li {
  height: 1.625rem;
  border-radius: 0.3125rem;
  background: color-mix(in srgb, var(--bf-muted) 12%, transparent);
  animation: bf-iuptime-in var(--bf-dur-side, 320ms) var(--bf-ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 18ms);
}

.bf-iuptime > li.ok {
  background: color-mix(in srgb, var(--bf-success) 40%, transparent);
}

.bf-iuptime > li.bad {
  background: color-mix(in srgb, var(--bf-danger) 62%, transparent);
}

@keyframes bf-iuptime-in {
  from {
    opacity: 0;
    transform: scaleY(0.4);
  }
}


.bf-icell .axis {
  display: flex;
  justify-content: space-between;
  font-size: 10.5px;
  color: var(--bf-muted);
}

.bf-icell > .key {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-icell > .key > li {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.bf-itimeline {
  display: flex;
  flex-direction: column;
  margin: 0.25rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-itimeline > li {
  position: relative;
  display: grid;
  grid-template-columns: 1.25rem minmax(0, 1fr) auto;
  gap: 0.75rem;
  align-items: start;
  padding-bottom: 1rem;
}

.bf-itimeline > li:last-child {
  padding-bottom: 0;
}

.bf-itimeline > li::before {
  content: "";
  position: absolute;
  left: calc(0.625rem - 0.5px);
  top: 1.25rem;
  bottom: 0;
  width: 1px;
  background: var(--bf-edge-line);
}

.bf-itimeline > li:last-child::before {
  display: none;
}

.bf-itimeline .dot {
  display: grid;
  place-items: center;
  width: 1.25rem;
  height: 1.25rem;
  border-radius: 50%;
  background: var(--bf-card-veil-hover);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-itimeline .dot::after {
  content: "";
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: 50%;
  background: var(--bf-off);
}

.bf-itimeline > li.ok .dot::after { background: var(--bf-success); }
.bf-itimeline > li.warn .dot::after { background: var(--bf-warning); }
.bf-itimeline > li.bad .dot::after { background: var(--bf-danger); }

.bf-itimeline b {
  display: block;
  font-size: var(--bf-text-body);
  font-weight: 600;
}

.bf-itimeline .diag {
  display: block;
  margin-top: 0.125rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-itimeline .val {
  font-size: var(--bf-text-ui);
  font-weight: 600;
  white-space: nowrap;
}

/* El catálogo: las tres cuentas en cifra y el mosaico de módulos, con el peso de sus permisos, en tonos del acero. */
.bf-icounts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 1.75rem;
  margin: 0;
  font-size: var(--bf-text-ui);
  color: var(--bf-muted);
}

.bf-icounts b {
  font-size: 1.5rem;
  font-weight: 600;
  color: var(--bf-surface-fg);
  font-variant-numeric: tabular-nums;
}

.bf-imosaic {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bf-imosaic > li {
  flex: var(--w, 1) 1 0;
  min-width: 5.5rem;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 4.75rem;
  padding: 0.5rem 0.625rem;
  border-radius: 0.5rem;
  background: rgb(var(--bf-isteel) / calc(0.3 - var(--r, 0) * 0.03));
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  font-size: var(--bf-text-label);
}

.bf-imosaic > li > b {
  font-size: var(--bf-text-ui);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-imosaic > li > span {
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-icatalog .bf-irows dd.mono {
  font-family: var(--bf-font-mono);
  font-size: 12px;
}

/* ── Datos: el formulario de siempre, agrupado en tarjetas con su cabecera ──
   Cada `.bf-form-row` pasa a ser LA tarjeta: el carril de leyenda sube a su cabecera (ficha neutra, título y la
   explicación debajo) y la sección deja de ser otra tarjeta dentro. El marcado no cambia: lo comparte con el alta. */
.bf-ific .bf-ipane .bf-form,
.bf-ific .bf-ipane .bf-iform {
  gap: 1rem;
}

.bf-ific .bf-ipane .bf-form-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 0;
  padding: 0;
  overflow: hidden;
}

.bf-ific .bf-ipane .bf-frail {
  align-items: flex-start;
  gap: 0.75rem;
  padding: 1rem 1.25rem 0.875rem;
  border-bottom: 1px solid var(--bf-edge-line);
}

.bf-ific .bf-ipane .bf-frail .fic {
  width: 1.75rem;
  height: 1.75rem;
  background: var(--bf-panel-bg);
  color: var(--bf-muted);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-ific .bf-ipane .bf-frail .fic > svg {
  width: 1rem;
  height: 1rem;
}

.bf-ific .bf-ipane .bf-frail h3 {
  font-size: var(--bf-text-body);
}

.bf-ific .bf-ipane .bf-frail p {
  max-width: 62rem;
}

.bf-ific .bf-ipane .bf-form-row > .bf-sect {
  outline: none;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ── Responsive (MF): la pestaña se compone por su PROPIO ancho; la ficha, por el de la vista ── */
/* Consulta de contenedor (I-2: no cuenta para MF-3). 760 px es el ancho de pestaña en que cada una de las seis
   columnas del mosaico baja de unos 113 px ((760 − 5 × 16) / 6): por debajo, una celda `.w2` ya no cabe su cifra y
   su rótulo, y el mosaico pasa a dos columnas. */
@container bf-ipane (max-width: 760px) {
  .bf-ibento {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem;
  }

  .bf-ibento > :is(.w3, .w4) {
    grid-column: 1 / -1;
  }

  .bf-ibento > .w2 {
    grid-column: span 1;
  }

  /* La celda estrecha que se quedaría sola en su renglón se lleva el renglón entero, y sus hechos van a dos columnas. */
  .bf-ibento > .w2.fill {
    grid-column: 1 / -1;
  }

  .bf-ibento > .fill .bf-ifacts2 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0.75rem 1.5rem;
  }

  .bf-ibento > .fill .bf-ifacts2 > div {
    padding: 0;
    border-top: 0;
  }

  .bf-ivig .ringrow {
    align-items: flex-start;
  }

  .bf-imonths {
    grid-template-columns: repeat(7, minmax(0, 1fr));
  }

  .bf-imonths > li:nth-child(n + 8) {
    display: none;
  }
}

/* Consulta de contenedor (I-2). A 480 px de pestaña cada una de las dos columnas mide unos 234 px ((480 − 12) / 2),
   lo justo para una celda con su anillo y sus cifras en fila; por debajo, todo a una columna y el anillo apilado. */
@container bf-ipane (max-width: 480px) {
  .bf-ibento > .w2 {
    grid-column: 1 / -1;
  }

  .bf-ivig .ringrow {
    flex-direction: column;
    gap: 0.75rem;
  }

  .bf-icell {
    padding: 1rem;
  }

  .bf-ipin .pinrow > form {
    margin-left: 0;
  }

  .bf-itimeline > li {
    grid-template-columns: 1.25rem minmax(0, 1fr);
  }

  .bf-itimeline > li > :is(.bf-pill, bf-pill, .val) {
    grid-column: 2;
    justify-self: start;
  }
}

/* A menos de 900 px (el corte del lateral) la columna pasa ARRIBA y deja de ser fija; su navegación vuelve a ser un
   carril horizontal que se desplaza dentro de sí mismo (MF-5). */
/* Excepción a MF-2: max-width porque la columna fija es la ficha de siempre (bloque de arriba) y por debajo del
   corte ancho solo se apila; escrita al revés habría que mover todo el bloque de escritorio a un min-width. */
@media (max-width: 899.98px) {
  .bf-ific {
    grid-template-columns: minmax(0, 1fr);
    gap: 1rem;
  }

  .bf-iside {
    position: static;
    padding: 1rem;
  }

  .bf-ific .bf-ivtabs {
    flex-direction: row;
    margin: 0;
    padding: 0.75rem 0 0;
    overflow-x: auto;
  }

  .bf-ific .bf-ivtabs > a {
    flex: none;
  }

  .bf-ific .bf-ivtabs > a .tail {
    display: none;
  }

  .bf-iside-facts {
    flex-direction: row;
    flex-wrap: wrap;
    gap: 0.375rem 1rem;
  }

  .bf-iside-facts > li {
    max-width: 100%;
  }
}

/* Excepción a MF-2: max-width porque solo esconde un dato y aprieta el relleno en el teléfono. */
@media (max-width: 619.98px) {
  .bf-iside-facts > li.mono {
    display: none;
  }

  .bf-ific .bf-ipane .bf-frail {
    padding: 0.875rem 1rem 0.75rem;
  }
}

/* Movimiento reducido: la tira entra sin crecer. Transparencia reducida: el velo se hace macizo. */
@media (prefers-reduced-motion: reduce) {
  .bf-iuptime > li {
    animation: none;
  }
}

:root[data-reduce~="motion"] .bf-iuptime > li {
  animation: none;
}

@media (prefers-reduced-transparency: reduce) {
  .bf-ific :is(.bf-iside, .bf-icell),
  .bf-ific .bf-ipane .bf-form-row {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-ific .bf-iside, .bf-ific .bf-icell, .bf-ific .bf-ipane .bf-form-row) {
  background-color: var(--bf-card-bg);
}

@media (forced-colors: active) {
  .bf-ific .bf-ipane .bf-form-row {
    outline-color: CanvasText;
  }

  .bf-imonths > li > i,
  .bf-iuptime > li,
  .bf-imosaic > li {
    border: 1px solid CanvasText;
  }
}

/* ═══ fin del bloque de la chore 90.787 ═══ */

/* ═══ LOS CONMUTADORES, CON UN POCO MÁS DE AIRE (chore 90.797) ═══════════════════════
   Petición del usuario: un poco más de relleno. El carril pasa de 2 a 3 px alrededor de la gota y cada opción de
   12 a 15 px a los lados (las pastillas de filtro, con su contador a la derecha, de 11/8 a 14/11). El alto no cambia:
   sigue siendo el de control, 1,75 rem. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc), .bf-fchips > .row) {
  padding: 3px;
  gap: 3px;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc)) > :is(a, button, label, i) {
  padding-inline: 0.9375rem;
}

.bf-fchips > .row {
  box-sizing: border-box;
  height: 1.75rem;
}

.bf-fchips > .row > :is(a, button, label) {
  height: 100%;
  padding-block: 0;
  padding-inline: 0.875rem 0.6875rem;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc), .bf-fchips > .row) > .bf-tabs-ink {
  top: 3px;
  bottom: 3px;
}

/* ═══ fin del bloque de la chore 90.797 ═══ */

/* ═══ LA GOTA, CON MENOS BRILLO (chore 90.798) ═════════════════════════════════════════
   Petición del usuario: el seleccionado de un conmutador brilla menos. Se baja la luz y la saturación que la gota da a
   lo que tiene detrás (claro ×1,06 → ×1,02; oscuro ×1,3 → ×1,1; saturación ×2 → ×1,3), su brillo especular y la brasa
   del acento de debajo. El canto (el filete) y el viaje no cambian. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 16%, transparent);
  --gota-lift: 1.02;
}

:root[data-theme="dark"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 4%, transparent);
  --gota-lift: 1.1;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 4%, transparent);
    --gota-lift: 1.1;
  }
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  -webkit-backdrop-filter: blur(0.6px) saturate(1.3) brightness(var(--gota-lift));
  backdrop-filter: blur(0.6px) saturate(1.3) brightness(var(--gota-lift));
  box-shadow:
    inset 0 0 3px 0 var(--gota-spec-soft),
    0 3px 5px -3px var(--gota-drop),
    0 4px 8px -6px color-mix(in srgb, var(--bf-accent) 10%, transparent);
}

/* ═══ fin del bloque de la chore 90.798 ═══ */

/* ═══ LOS ACORDEONES DE PERMISOS SE ABREN Y SE CIERRAN SUAVES (chore 90.801) ═══════════
   Petición del usuario: los módulos del árbol de permisos (`.bf-tree-group`, en el catálogo de permisos y en los
   permisos de un rol) crecen al abrirse y se recogen al cerrarse con la curva de salida de la casa, en vez de aparecer y
   desaparecer de golpe. Es la receta de los otros desplegables (.bf-filterbox, Mi perfil): el contenido del <details>
   (`::details-content`) anima su alto de 0 a `auto` con `interpolate-size` y funde; `content-visibility` entra en la
   transición para que al cerrar aguante pintado lo que dura. Sigue siendo un <details> nativo y sin JavaScript; el
   navegador que no sepa animar `auto` lo hace seco. Movimiento reducido: seco. El galón ya giraba. */
.bf-tree:has(> .bf-tree-group),
.bf-tree-wrap:has(.bf-tree-group) {
  interpolate-size: allow-keywords;
}

.bf-tree-group::details-content {
  block-size: 0;
  opacity: 0;
  overflow-y: clip;
  transition:
    block-size 320ms var(--bf-ease-out),
    opacity 260ms var(--bf-ease-out),
    content-visibility 320ms allow-discrete;
}

.bf-tree-group[open]::details-content {
  block-size: auto;
  opacity: 1;
}

.bf-tree-group > summary .caret {
  transition: transform 320ms var(--bf-ease-out);
}

@media (prefers-reduced-motion: reduce) {
  .bf-tree-group::details-content,
  .bf-tree-group > summary .caret {
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-tree-group::details-content {
  transition: none;
}

:root[data-reduce~="motion"] .bf-tree-group > summary .caret {
  transition: none;
}

/* ═══ fin del bloque de la chore 90.801 ═══ */

/* ═══ EL CTA, MÁS CLARO Y MÁS PREMIUM (chore 90.802) ══════════════════════════════════
   Petición del usuario: el tono de los CTA algo más claro (tokens.css: --bf-cta y --bf-cta-hover) y más premium. Deja de
   ser el relleno plano de Hugin (90.627): un degradado vertical muy leve de su propio color —un 10 % más claro arriba—,
   una línea de luz interior en el canto superior y una caída corta de su color, como una pieza esmaltada. Al apuntar
   se aclara y la caída crece un poco; al pulsar se hunde (sin luz y un 12 % más oscuro). Sin transformaciones: el
   botón no se mueve. Con «sin cristal» o contraste alto, plano como antes. */
.bf-btn.primary {
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--bf-cta), var(--bf-relief-light) 10%), var(--bf-cta));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--bf-relief-light) 22%, transparent),
    0 1px 2px color-mix(in srgb, var(--bf-cta) 30%, transparent),
    0 4px 10px -6px color-mix(in srgb, var(--bf-cta) 55%, transparent);
}

.bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) {
  background-image: linear-gradient(180deg, color-mix(in srgb, var(--bf-cta-hover), var(--bf-relief-light) 10%), var(--bf-cta-hover));
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--bf-relief-light) 26%, transparent),
    0 1px 2px color-mix(in srgb, var(--bf-cta) 30%, transparent),
    0 6px 14px -7px color-mix(in srgb, var(--bf-cta) 65%, transparent);
}

.bf-btn.primary:active:not(:disabled):not([aria-disabled="true"]) {
  background-image: none;
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 18%, transparent);
}

@media (prefers-contrast: more) {
  .bf-btn.primary,
  .bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) {
    background-image: none;
    box-shadow: none;
  }
}

/* ═══ fin del bloque de la chore 90.802 ═══ */

/* ═══ EL CATÁLOGO DE PERMISOS, LEÍDO POR ROL (chore 90.795) ═══════════════════════════════
   Petición del usuario, elegida entre los mockups (variante «Por rol»): la pantalla debe entenderse de un vistazo.
     · Arriba, los PRODUCTOS como un conmutador segmentado GRANDE (2,875 rem) con la gota de la casa: baldosa neutra con
       iniciales, nombre y «N permisos». Rueda en horizontal; flechas y bordes desvanecidos sólo donde queda algo por ver
       (los marca ts/productCatalogPermissions con `data-more-start` / `data-more-end`).
     · La vista principal es la LECTURA POR ROL: roles a la izquierda («N de M permisos»), a la derecha «Qué puede hacer
       <rol> en <producto>», contadores Acceso / Ver / Editar / Administrar y, por módulo (acordeón `.bf-tree-group`, que
       anima la 90.801), la tabla Función × grupos con Sí / No / —, más el aviso «sin acceso».
     · La tarjeta ocupa el alto de la vista (fillToView, `--fill-h`) y rueda por dentro; en teléfono, una columna y rueda
       la página (MF). El fondo de la lectura es el de las tablas de Hugin (90.781). Sin colores por producto: el acento
       sólo como luz. */

/* ── El conmutador grande de productos ── */
.bf-catprod {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  margin-top: 0.875rem;
}

.bf-catprod-scroll {
  --fade-s: 0px;
  --fade-e: 0px;
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  padding: 3px 2px;
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 var(--fade-s), #000 calc(100% - var(--fade-e)), transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 var(--fade-s), #000 calc(100% - var(--fade-e)), transparent 100%);
}

.bf-catprod-scroll::-webkit-scrollbar {
  display: none;
}

.bf-catprod[data-more-start] .bf-catprod-scroll {
  --fade-s: 1.5rem;
}

.bf-catprod[data-more-end] .bf-catprod-scroll {
  --fade-e: 1.5rem;
}

.bf-catprod-arrow {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  padding: 0;
  border: 0;
  border-radius: 999px;
  color: var(--bf-muted);
  background: color-mix(in srgb, var(--bf-head-glass) 45%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  cursor: pointer;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out), color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-catprod-arrow[hidden] {
  display: none;
}

.bf-catprod-arrow > svg {
  width: 14px;
  height: 14px;
}

.bf-catprod-arrow:hover:not(:disabled) {
  background-color: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-catprod-arrow:disabled {
  opacity: 0.35;
  cursor: default;
}

.bf-catprod .bf-seg.bf-seg-big {
  max-width: none;
  height: 2.875rem;
  padding: 3px;
  gap: 3px;
}

.bf-catprod .bf-seg.bf-seg-big > a {
  height: 100%;
  gap: 0.625rem;
  padding: 0 1rem 0 0.3125rem;
  font-size: 0.9375rem;
}

.bf-catprod .bf-seg.bf-seg-big .tl {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.125rem;
  height: 2.125rem;
  border-radius: 999px;
  font-size: var(--bf-text-mini);
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--bf-muted);
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  transition: color 320ms var(--bf-ease-out), box-shadow 320ms var(--bf-ease-out);
}

.bf-catprod .bf-seg.bf-seg-big .tx {
  display: grid;
  text-align: left;
  line-height: 1.15;
}

.bf-catprod .bf-seg.bf-seg-big .tx small {
  font-size: var(--bf-text-mini);
  font-weight: 400;
  color: var(--bf-dim);
}

/* La puesta: la baldosa recoge la luz del acento. Con gota la marca `data-gota-on`; sin script, `.on`. */
.bf-catprod .bf-seg.bf-seg-big > [data-gota-on] .tl,
.bf-catprod .bf-seg.bf-seg-big:not(:has(> .bf-tabs-ink)) > .on .tl {
  color: var(--bf-selected-ink);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 45%, transparent),
    0 0 12px -3px color-mix(in srgb, var(--bf-accent) 50%, transparent);
}

/* Grande, se amplía menos: un 3 % y no un 5 %. */
.bf-catprod .bf-seg.bf-seg-big:has(> .bf-tabs-ink):not([data-glide]) > [data-gota-on] {
  transform: scale(1.03);
}

/* ── La guía ── */
.bf-page-header .lead .bf-catguide-link {
  color: var(--bf-selected-ink);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}

.bf-page-header .lead .bf-catguide-link:hover {
  text-decoration: underline;
}

/* ── La tarjeta de la lectura ── */
.bf-catread {
  --ht-panel: var(--bf-panel-color);
  --ht-veil: 60%;
  display: grid;
  grid-template-columns: 16rem minmax(0, 1fr);
  padding: 0;
  overflow: hidden;
}

:root[data-theme="dark"] .bf-catread {
  --ht-panel: var(--bf-panel-color);
  --ht-veil: 24%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-catread {
    --ht-panel: var(--bf-panel-color);
    --ht-veil: 24%;
  }
}

@media (min-width: 620px) {
  .bf-card.bf-catread[data-fill][data-fit="view"] {
    display: grid;
    grid-template-rows: minmax(0, 1fr);
    height: var(--fill-h, auto);
    min-height: 0;
  }
}

.bf-catread-empty {
  grid-column: 1 / -1;
  display: grid;
  place-items: center;
  align-content: center;
  gap: 1rem;
  padding: 2.5rem 1.5rem;
}

/* Los roles: una lista de enlaces que hace de conmutador (la capa la trata como tal: `data-view-links`). */
.bf-catread-roles {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
  min-height: 0;
  overflow: auto;
  padding: 0.75rem;
  border-right: 1px solid var(--bf-edge-line);
  scrollbar-width: thin;
}

.bf-catread-cap {
  margin: 0.125rem 0.5rem 0.375rem;
  font-size: var(--bf-text-mini);
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-catread-role {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: 0.125rem 0.625rem;
  padding: 0.625rem 0.75rem;
  border-radius: 0.75rem;
  color: inherit;
  text-decoration: none;
  transition: background-color var(--bf-dur-fast) var(--bf-ease-out);
}

.bf-catread-role:hover {
  background-color: var(--bf-hover);
  text-decoration: none;
}

.bf-catread-role.on {
  background-color: var(--bf-card-veil);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-role.on::before {
  content: "";
  position: absolute;
  left: -0.75rem;
  top: 0.75rem;
  bottom: 0.75rem;
  width: 2px;
  border-radius: 2px;
  background: var(--bf-accent);
  box-shadow: 0 0 8px var(--bf-accent);
}

.bf-catread-role .ini {
  grid-row: 1 / span 2;
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 999px;
  font-size: var(--bf-text-label);
  font-weight: 700;
  color: var(--bf-surface-fg);
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-role.on .ini {
  color: var(--bf-selected-ink);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 45%, transparent);
}

.bf-catread-role .rn {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
  font-size: 0.875rem;
  font-weight: 600;
}

.bf-catread-role.off .rn {
  color: var(--bf-muted);
}

.bf-catread-role .rs {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  font-variant-numeric: tabular-nums;
}

.bf-catread-role .bf-catmeter {
  grid-column: 2;
  margin-top: 0.25rem;
}

/* La barra: todo (verde), una parte (ámbar), nada (vacía). */
.bf-catmeter {
  display: block;
  min-width: 3rem;
  height: 4px;
  border-radius: 4px;
  overflow: hidden;
  background: var(--bf-border);
}

.bf-catmeter > i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--bf-warning);
}

.bf-catmeter.on > i {
  background: var(--bf-success);
}

.bf-catmeter.none > i {
  background: transparent;
}

/* ── La lectura ── */
.bf-catread-main {
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.bf-catread-hero {
  flex: none;
  padding: 1rem 1.125rem 0.75rem;
}

.bf-catread-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin: 0.75rem 0 0;
  padding: 0;
  list-style: none;
}

.bf-catread-tiles li {
  display: grid;
  min-width: 8.5rem;
  padding: 0.5rem 0.75rem;
  border-radius: 0.75rem;
  background: var(--bf-card-veil);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-tiles .tn {
  font-size: var(--bf-text-mini);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-catread-tiles .tv {
  font-size: 1.25rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

.bf-catread-tiles .tv small {
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--bf-muted);
}

.bf-catread-tiles .td {
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-catread-bar {
  flex: none;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.75rem;
  padding: 0.25rem 1.125rem 0.75rem;
}

.bf-catread-find {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex: 0 1 20rem;
  min-width: 13rem;
}

.bf-catread-find .bf-field-search {
  flex: 1 1 auto;
  min-width: 0;
}

.bf-catread-lbl {
  font-size: var(--bf-text-mini);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

/* Lo que rueda: los módulos, sobre el fondo de las tablas de Hugin. */
.bf-catread-scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  border-top: 1px solid var(--bf-edge-line);
  background-color: color-mix(in srgb, var(--ht-panel) var(--ht-veil), transparent);
  scrollbar-width: thin;
}

.bf-catread-scroll > :is(bf-empty, .bf-empty) {
  padding: 2.5rem 1rem;
}

.bf-catread-mod {
  border-bottom: 1px solid var(--bf-border);
}

.bf-catread-mod > summary {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.6875rem 1.125rem;
  background-color: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) + 28%), var(--bf-card-bg));
}

.bf-catread-mod > summary:hover {
  background-image: linear-gradient(var(--bf-hover), var(--bf-hover));
}

.bf-catread-mod[open] > summary {
  box-shadow: inset 0 -1px 0 var(--bf-border-strong);
}

.bf-catread-mod > summary .caret {
  flex: none;
  width: 0.75rem;
  text-align: center;
  color: var(--bf-muted);
}

.bf-catread-mod > summary .mn {
  font-size: 1rem;
  font-weight: 600;
}

.bf-catread-mod > summary .mc {
  margin-left: auto;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.bf-catread-mod > summary .bf-catmeter {
  flex: none;
  width: 5rem;
}

.bf-catread-gridwrap {
  overflow-x: auto;
}

.bf-catread-grid {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.bf-catread-grid thead th {
  padding: 0.5rem 0.75rem;
  border-bottom: 1px solid var(--bf-border);
  text-align: left;
  white-space: nowrap;
  font-size: var(--bf-text-mini);
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-dim);
}

.bf-catread-grid thead th small {
  display: block;
  font-weight: 400;
  letter-spacing: 0;
  text-transform: none;
}

.bf-catread-grid :is(thead, tbody) :is(th, td):first-child {
  padding-left: 2.5rem;
}

.bf-catread-grid tbody :is(th, td) {
  padding-block: 0.5rem;
  padding-right: 0.75rem;
  border-bottom: 1px solid var(--bf-border);
  vertical-align: middle;
  text-align: left;
}

.bf-catread-grid tbody td {
  padding-left: 0.75rem;
}

.bf-catread-grid tbody tr:last-child > * {
  border-bottom: 0;
}

.bf-catread-grid tbody tr:hover > * {
  background-color: var(--bf-hover);
}

.bf-catread-grid tbody th {
  min-width: 12rem;
  font-weight: 500;
}

.bf-catacts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

/* Las marcas en llano: «Sí» / la acción con su visto, «No» / la acción con su raya. */
.bf-catmark {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  height: 1.5rem;
  padding: 0 0.5625rem 0 0.375rem;
  border-radius: 999px;
  font-size: var(--bf-text-label);
  white-space: nowrap;
  vertical-align: middle;
}

.bf-catmark::before {
  content: "";
  flex: none;
  width: 0.875rem;
  height: 0.875rem;
  background: currentColor;
  -webkit-mask: var(--bf-catmark-glyph) center / contain no-repeat;
  mask: var(--bf-catmark-glyph) center / contain no-repeat;
}

.bf-catmark.yes {
  --bf-catmark-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12.5 10 17l9-10'/%3E%3C/svg%3E");
  font-weight: 600;
  color: var(--bf-success-text);
  background: color-mix(in srgb, var(--bf-success) 14%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-success) 32%, transparent);
}

.bf-catmark.no {
  --bf-catmark-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.6' stroke-linecap='round'%3E%3Cpath d='M7 7l10 10M17 7 7 17'/%3E%3C/svg%3E");
  color: var(--bf-dim);
  box-shadow: inset 0 0 0 1px var(--bf-border);
}

.bf-catnone {
  color: var(--bf-dim);
}

.bf-catread .untr {
  font-style: italic;
}

.bf-catread-warn {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
  font-size: var(--bf-text-label);
  color: var(--bf-warning-text);
  white-space: nowrap;
}

.bf-catread-warn > svg {
  flex: none;
  width: 13px;
  height: 13px;
}

.bf-catread-warn.solo {
  margin-left: 0.375rem;
  vertical-align: middle;
}

/* ── Teléfono: la página rueda (MF). La columna única y el carril de roles van en el bloque de 1100, más abajo ── */
@media (max-width: 619px) {
  .bf-catprod-arrow:not([hidden]) {
    display: none;
  }

  .bf-catprod .bf-seg.bf-seg-big > a {
    padding-right: 0.75rem;
  }

  .bf-catprod .bf-seg.bf-seg-big .tx small {
    display: none;
  }

  .bf-catread-hero,
  .bf-catread-bar {
    padding-inline: 0.875rem;
  }

  .bf-catread-find {
    flex: 1 1 100%;
  }

  .bf-catread-tiles li {
    flex: 1 1 6.5rem;
    min-width: 0;
  }

  .bf-catread-scroll {
    overflow: visible;
  }

  .bf-catread-mod > summary {
    position: static;
    flex-wrap: wrap;
    padding-inline: 0.875rem;
  }

  .bf-catread-mod > summary .bf-catmeter {
    display: none;
  }

  .bf-catread-grid :is(thead, tbody) :is(th, td):first-child {
    padding-left: 0.875rem;
  }

  .bf-catread-grid tbody th {
    min-width: 9.5rem;
  }
}

/* Movimiento reducido: seco. Transparencia reducida: lo que rueda, macizo. */
@media (prefers-reduced-motion: reduce) {
  .bf-catprod-arrow,
  .bf-catread-role,
  .bf-catprod .bf-seg.bf-seg-big .tl {
    transition: none;
  }

  .bf-catprod .bf-seg.bf-seg-big > [data-gota-on] {
    transform: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-catprod-arrow, .bf-catread-role, .bf-catprod .bf-seg.bf-seg-big .tl) {
  transition: none;
}

@media (prefers-reduced-transparency: reduce) {
  .bf-catread-scroll,
  .bf-catread-tiles li,
  .bf-catread-role.on {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-catread-scroll, .bf-catread-tiles li, .bf-catread-role.on) {
  background-color: var(--bf-card-bg);
}

/* ═══ fin del bloque de la chore 90.795 ═══ */

/* ═══ EL CATÁLOGO DE PERMISOS, REDISEÑADO (chore 90.987, petición del usuario, 2026-10-08) ═══════════════════════════
   «Rediseñar la vista de Catálogo para que sea visualmente más atractiva y ordenada, con el texto de ayuda más corto;
   premium e impactante, porque es elaborado.» Lo que se lee primero es ahora una CIFRA y no una frase:
     · la cabecera de la lectura lleva un ANILLO con lo que el rol tiene del producto (se dibuja una vez al llegar),
       quién y dónde, y una FRANJA con los cuatro grupos —su glifo, la cifra, la barra y qué quieren decir— en una sola
       superficie partida por filetes (M-5), no cuatro tarjetas sueltas;
     · los roles llevan ese mismo anillo alrededor de sus iniciales y el porcentaje a la derecha;
     · ORDEN: las columnas son las del producto en TODOS los módulos, con el mismo ancho, bajo UNA cabecera que se queda
       fija al rodar (y la de cada módulo debajo de ella); antes cada módulo traía sus columnas y sus anchos;
     · lo concedido se ve y lo que no, calla: la marca «No» pierde el filete y queda en un velo tenue.
   El verde, el ámbar y la barra vacía siguen diciendo TODO, UNA PARTE y NADA (M-2); el acento, sólo en el rol elegido.
   Sin halos ni luces de color (lo descartado). Por debajo de 46 rem de lectura, cada tabla vuelve a su cabecera y rueda
   por su cuenta; en teléfono, una columna (MF). */

.bf-catread {
  grid-template-columns: 17.5rem minmax(0, 1fr);
}

/* El tono de cada pieza: todo (verde), una parte (ámbar), nada (el filete). */
.bf-catread [data-tone="on"] { --cat-tone: var(--bf-success); --cat-ink: var(--bf-success-text); }
.bf-catread [data-tone="part"] { --cat-tone: var(--bf-warning); --cat-ink: var(--bf-warning-text); }
.bf-catread [data-tone="none"] { --cat-tone: transparent; --cat-ink: var(--bf-dim); }

/* ── Los roles ── */
.bf-catread-roles {
  gap: 0.25rem;
  padding: 1.125rem 0.75rem;
  background-color: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) * 0.5), transparent);
}

.bf-catread-cap {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0.5rem 0.625rem;
}

.bf-catread-cap .n {
  display: inline-grid;
  place-items: center;
  min-width: 1.375rem;
  height: 1.375rem;
  padding: 0 0.375rem;
  border-radius: 999px;
  font-size: var(--bf-text-mini);
  letter-spacing: 0;
  font-variant-numeric: tabular-nums;
  color: var(--bf-muted);
  background: var(--bf-card-veil);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-role {
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.0625rem 0.75rem;
  padding: 0.625rem 0.75rem 0.625rem 0.625rem;
  border-radius: 0.875rem;
}

/* Las iniciales dentro de su anillo: el aro es un degradado cónico recortado a 2,5 px; el disco, la baldosa neutra. */
.bf-catread-role .ini {
  position: relative;
  isolation: isolate;
  width: 2.625rem;
  height: 2.625rem;
  font-size: var(--bf-text-label);
  letter-spacing: 0.02em;
  background: none;
  box-shadow: none;
}

.bf-catread-role .ini::before,
.bf-catread-role .ini::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: 50%;
}

.bf-catread-role .ini::before {
  inset: 0;
  background: conic-gradient(var(--cat-tone) calc(var(--p, 0) * 1%), var(--bf-border) 0);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 2.5px), #000 calc(100% - 2px));
}

.bf-catread-role .ini::after {
  inset: 4px;
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-role .rs {
  font-size: var(--bf-text-mini);
}

.bf-catread-role .pc {
  grid-column: 3;
  grid-row: 1 / span 2;
  font-size: 0.875rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  color: var(--cat-ink);
}

.bf-catread-role .pc small,
.bf-catmod-ring small {
  margin-left: 0.0625rem;
  font-size: 0.7em;
  font-weight: 500;
  opacity: 0.8;
}

/* El elegido: el velo de la casa con la luz del acento en su esquina y su canto (M-9). */
.bf-catread-role.on {
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--bf-accent) 10%, transparent), transparent 62%),
    var(--bf-card-veil);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 28%, var(--bf-edge-line));
}

/* ── La cabecera de la lectura: sólo la franja de grupos (el anillo y el título repetían la tarjeta del rol) ── */
.bf-catread-hero {
  padding: 1rem 1.5rem;
}

/* La franja de los grupos: UNA superficie, partida por filetes. Cada pieza pinta el suyo arriba y a la izquierda y la
   franja recorta los del borde, así vale igual en una fila que en dos. */
.bf-catread-tiles {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
  gap: 0;
  margin: 0;
  overflow: hidden;
  border-radius: 0.875rem;
  background: var(--bf-card-veil);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

/* Cada pieza en tres líneas: el glifo, el grupo con lo que quiere decir debajo y la cifra a la derecha; la barra al pie. */
.bf-catread-tiles li {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  grid-template-rows: auto auto auto;
  align-items: center;
  gap: 0 0.625rem;
  min-width: 0;
  padding: 0.75rem 1rem 0.8125rem;
  border-radius: 0;
  background: none;
  box-shadow: -1px 0 0 var(--bf-edge-line), 0 -1px 0 var(--bf-edge-line);
}

.bf-catread-tiles .ic {
  grid-column: 1;
  grid-row: 1 / span 2;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 0.5625rem;
  color: var(--cat-ink);
  background: color-mix(in srgb, var(--cat-tone) 14%, var(--bf-panel-bg));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--cat-tone) 30%, var(--bf-edge-line));
}

.bf-catread-tiles .ic > svg {
  width: 15px;
  height: 15px;
}

.bf-catread-tiles .tn {
  grid-column: 2;
  grid-row: 1;
  align-self: end;
}

.bf-catread-tiles .td {
  grid-column: 2;
  grid-row: 2;
  align-self: start;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.bf-catread-tiles .tv {
  grid-column: 3;
  grid-row: 1 / span 2;
  font-size: 1.5rem;
  font-weight: 650;
  line-height: 1;
  letter-spacing: -0.01em;
  white-space: nowrap;
}

.bf-catread-tiles .bf-catmeter {
  grid-column: 1 / -1;
  grid-row: 3;
  margin-top: 0.625rem;
}

/* ── La barra: la búsqueda a la izquierda, el filtro a la derecha ── */
.bf-catread-bar {
  padding: 0 1.5rem 1rem;
}

.bf-catread-find {
  flex: 0 1 22rem;
}

.bf-catread-lbl {
  margin-left: auto;
}

/* ── Lo que rueda: una cabecera de columnas para todos los módulos ── */
.bf-catread-main {
  container: catread / inline-size;
}

.bf-catread .bf-tree {
  overflow: clip;
}

.bf-catread-cols {
  display: none;
}

/* ── El módulo: su cabecera es una FILA DE RESUMEN («Adquisición y servicio», segunda vuelta de la 90.987) ──
   A la izquierda, quién es: el galón, un anillo con lo que el rol tiene del módulo, el nombre y debajo «N funciones ·
   X de Y permisos» con el aviso. En las columnas (a lo ancho), lo que tiene de cada grupo: la cifra y una barra de UN
   SEGMENTO POR PERMISO (con más de doce, la barra continua). En estrecho, sólo la identidad: el anillo ya dice cuánto. */
.bf-catread-mod > summary {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem 1.5rem 0.875rem 1.125rem;
}

.bf-catmod-who {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex: 1 1 auto;
  min-width: 0;
}

.bf-catmod-ring {
  flex: none;
  position: relative;
  isolation: isolate;
  display: flex;
  align-items: center;
  justify-content: center;
  white-space: nowrap;
  width: 2.625rem;
  height: 2.625rem;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--cat-ink);
}

.bf-catmod-ring::before,
.bf-catmod-ring::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: 50%;
}

.bf-catmod-ring::before {
  inset: 0;
  background: conic-gradient(var(--cat-tone) calc(var(--p, 0) * 1%), var(--bf-border) 0);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
  mask: radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 2.5px));
}

.bf-catmod-ring::after {
  inset: 5px;
  background: var(--bf-panel-bg);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-catread-mod[data-tone="none"] .bf-catmod-ring,
.bf-catread-mod > summary[data-tone="none"] .bf-catmod-ring {
  color: var(--bf-dim);
}

.bf-catmod-tx {
  display: grid;
  gap: 0.1875rem;
  min-width: 0;
}

.bf-catread-mod > summary .mn {
  overflow: hidden;
  font-size: 1.0625rem;
  font-weight: 650;
  letter-spacing: -0.005em;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.bf-catmod-sub {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.125rem 0.75rem;
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-catmod-sub .ms {
  font-variant-numeric: tabular-nums;
}

.bf-catmod-col {
  display: none;
}

.bf-catmod-col .cv {
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.bf-catmod-col .cv b {
  font-size: 1.25rem;
  font-weight: 650;
  letter-spacing: -0.01em;
  color: var(--cat-ink);
}

.bf-catmod-col[data-tone="none"] .cv b {
  color: var(--bf-surface-fg);
}

.bf-catmod-col .cv small {
  margin-left: 0.125rem;
  font-size: 0.8125rem;
  color: var(--bf-muted);
}

/* La max-width de aquí y la de .bf-catseg no son cortes de pantalla (MF-2 habla de consultas): son el tope de largo
   de la barra, para que no se estire a todo el ancho de la columna. */
.bf-catmod-col .bf-catmeter {
  width: auto;
  max-width: 8.5rem;
}

/* La barra de segmentos: uno por permiso, encendidos los que el rol tiene. */
.bf-catseg {
  display: flex;
  gap: 3px;
  max-width: 8.5rem;
}

.bf-catseg > i {
  flex: 1 1 0;
  height: 5px;
  border-radius: 3px;
  background: var(--bf-border);
}

.bf-catseg > i.on {
  background: var(--cat-tone);
}

/* ── Las funciones ── */
.bf-catread-grid tbody :is(th, td) {
  padding-block: 0.6875rem;
}

/* Las marcas, como piezas: lo concedido, un cristal verde con su luz arriba; lo que no, un hueco punteado. */
.bf-catmark {
  height: 1.625rem;
  padding: 0 0.625rem 0 0.4375rem;
  border-radius: 0.5rem;
}

.bf-catmark.yes {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--bf-success) 22%, transparent),
    color-mix(in srgb, var(--bf-success) 11%, transparent));
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--bf-success) 36%, transparent),
    inset 0 1px 0 var(--bf-catmark-hi);
}

.bf-catread-grid tbody th .fnn {
  font-weight: 600;
}

/* Consulta de contenedor (I-2). Desde 46 rem de columna, las cuatro de grupo (lo que deja el 26 % del nombre) miden
   unos 8,5 rem cada una: lo que piden la cifra y la barra de segmentos. Por debajo, solo la identidad del módulo. */
@container catread (min-width: 46rem) {
  .bf-catread-cols {
    position: sticky;
    top: 0;
    z-index: 3;
    display: grid;
    grid-template-columns: 26% repeat(var(--cols, 4), minmax(0, 1fr));
    align-items: center;
    height: 2.375rem;
    border-bottom: 1px solid var(--bf-border-strong);
    background-color: color-mix(in srgb, var(--ht-panel) calc(var(--ht-veil) + 40%), var(--bf-card-bg));
    font-size: var(--bf-text-mini);
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--bf-dim);
  }

  .bf-catread-cols > span {
    display: flex;
    align-items: center;
    gap: 0.4375rem;
    min-width: 0;
    padding: 0 0.75rem;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .bf-catread-cols > span:first-child {
    padding-left: 2.5rem;
  }

  .bf-catread-cols svg {
    flex: none;
    width: 13px;
    height: 13px;
  }

  /* El resumen del módulo, en las MISMAS columnas que la cabecera y las filas. */
  .bf-catread-mod > summary {
    top: 2.375rem;
    display: grid;
    grid-template-columns: 26% repeat(var(--cols, 4), minmax(0, 1fr));
    gap: 0;
    padding: 0.875rem 0;
  }

  .bf-catmod-who {
    padding: 0 0.75rem 0 1.125rem;
  }

  .bf-catmod-col {
    display: grid;
    align-content: center;
    gap: 0.4375rem;
    min-width: 0;
    padding: 0 1.25rem 0 0.75rem;
  }

  .bf-catmod-col > .bf-catnone {
    color: var(--bf-dim);
  }

  /* LA LÍNEA DEL MÓDULO: baja desde su galón y une sus funciones; en cada una, un punto con su tono —todo, una parte
     o nada (hueco)—. Los filetes entre funciones empiezan pasada la línea. */
  .bf-catread-grid tbody th[scope="row"] {
    position: relative;
    padding-left: 2.75rem;
  }

  .bf-catread-grid tbody th::before,
  .bf-catread-grid tbody th::after {
    content: "";
    position: absolute;
    left: 1.5rem;
    pointer-events: none;
  }

  .bf-catread-grid tbody th::before {
    top: 0;
    bottom: 0;
    width: 1px;
    margin-left: -0.5px;
    background: var(--bf-border-strong);
  }

  .bf-catread-grid tbody tr:last-child th::before {
    bottom: 50%;
  }

  .bf-catread-grid tbody th::after {
    top: 50%;
    width: 9px;
    height: 9px;
    margin: -4.5px 0 0 -4.5px;
    border-radius: 50%;
    background: var(--bf-card-bg);
    box-shadow: inset 0 0 0 1.5px var(--bf-border-strong);
  }

  .bf-catread-grid tbody tr[data-tone="on"] th::after {
    background: var(--bf-success);
    box-shadow: none;
  }

  .bf-catread-grid tbody tr[data-tone="part"] th::after {
    background: var(--bf-warning);
    box-shadow: none;
  }

  .bf-catread-grid tbody :is(th, td) {
    border-bottom-color: transparent;
    background-image: linear-gradient(var(--bf-border), var(--bf-border));
    background-repeat: no-repeat;
    background-size: 100% 1px;
    background-position: 0 100%;
  }

  .bf-catread-grid tbody th {
    background-size: calc(100% - 2.75rem) 1px;
    background-position: 100% 100%;
  }

  .bf-catread-grid tbody tr:last-child > * {
    background-image: none;
  }

  .bf-catread-cols > span:first-child {
    padding-left: 2.75rem;
  }

  .bf-catread-gridwrap {
    overflow-x: visible;
  }

  .bf-catread-grid {
    table-layout: fixed;
  }

  .bf-catread-grid col.fn {
    width: 26%;
  }

  .bf-catread-grid tbody th {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  /* Con las columnas fijas, una acción larga («Activar y desactivar plantillas de documento») parte su línea dentro
     de la celda en vez de salirse de ella. */
  .bf-catread-grid .bf-catmark {
    height: auto;
    min-height: 1.5rem;
    max-width: 100%;
    padding-block: 0.1875rem;
    white-space: normal;
    line-height: 1.25;
  }

  /* La cabecera de cada tabla, sólo para el lector de pantalla: la de arriba la sustituye a la vista. */
  .bf-catread-grid thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* LA CABECERA, SÓLO LA FRANJA. El anillo y el «Permisos de X en Y» repetían la tarjeta del rol elegido y se quitaron
   de la vista; queda la franja de grupos, más apretada, y ese alto se lo lleva la rejilla. Consulta de contenedor
   (I-2): desde 46 rem, donde la rejilla ya va en columnas. */
@container catread (min-width: 46rem) {
  .bf-catread-hero {
    padding: 0.875rem 1.5rem 0.75rem;
  }

  .bf-catread-tiles li {
    padding: 0.5rem 0.75rem 0.5625rem;
  }

  .bf-catread-tiles .ic {
    width: 1.75rem;
    height: 1.75rem;
  }

  .bf-catread-tiles .tv {
    font-size: 1.25rem;
  }

  .bf-catread-tiles .bf-catmeter {
    margin-top: 0.4375rem;
  }

  .bf-catread-bar {
    padding-bottom: 0.75rem;
  }

  /* El resumen del módulo, con menos aire: la cifra y la barra caben igual. */
  .bf-catread-mod > summary {
    padding-block: 0.625rem;
  }

  .bf-catread-grid tbody :is(th, td) {
    padding-block: 0.5rem;
  }
}

/* Lo concedido se ve; lo que no, calla: un hueco con el borde punteado, sin fondo. */
.bf-catmark.no {
  box-shadow: none;
  background: none;
  outline: 1px dashed color-mix(in srgb, var(--bf-surface-fg) 20%, transparent);
  outline-offset: -1px;
}

/* Por debajo de 1100 (MF-3), una columna: los roles pasan a un carril que rueda encima de la rejilla. Con la columna
   de roles al lado (17,5 rem), a 620 px a la rejilla le quedaban 224 px para 640 de contenido y había que leerla
   rodándola; ahora se lleva el ancho entero y, desde unos 900 px, cabe sin rodar. Va aquí, detrás del rediseño, porque
   la columna de 17,5 rem se declara sin media query y tiene que perder. */
@media (max-width: 1099.98px) {
  .bf-catread {
    grid-template-columns: minmax(0, 1fr);
  }

  /* La fila del carril mide lo que sus tarjetas. De 620 a 1100 la tarjeta se ajusta al alto de la vista con una sola
     fila (`[data-fit="view"]`, arriba), y sin esto la de los roles se encogía hasta 0 px y dejaba el carril tapado.
     Mismo selector que aquella regla, para ganarle. */
  .bf-card.bf-catread[data-fill][data-fit="view"] {
    grid-template-rows: max-content minmax(0, 1fr);
  }

  .bf-catread-roles {
    flex-direction: row;
    overflow-x: auto;
    padding: 0.625rem 0.75rem 0.75rem;
    border-right: 0;
    border-bottom: 1px solid var(--bf-edge-line);
    scrollbar-width: none;
  }

  .bf-catread-cap {
    display: none;
  }

  .bf-catread-role {
    flex: none;
    min-width: 12.5rem;
  }
}

@media (max-width: 619px) {
  .bf-catread-roles {
    padding: 0.5rem 0.625rem 0.75rem;
  }

  .bf-catread-hero {
    padding: 0.75rem 0.875rem;
  }

  .bf-catread-cap {
    display: none;
  }

  /* La franja, en dos columnas: el glifo, el grupo y la cifra debajo; lo que quiere decir, fuera (está en la cabecera). */
  .bf-catread-tiles {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .bf-catread-tiles li {
    grid-template-columns: auto minmax(0, 1fr);
    padding: 0.625rem 0.75rem 0.6875rem;
  }

  .bf-catread-tiles .td {
    display: none;
  }

  .bf-catread-tiles .tv {
    grid-column: 2;
    grid-row: 2;
    justify-self: start;
    margin-top: 0.1875rem;
    font-size: 1.125rem;
  }


  .bf-catread-bar {
    padding-inline: 0.875rem;
  }

  .bf-catread-lbl {
    margin-left: 0;
  }

  .bf-catread-mod > summary {
    padding-inline: 0.875rem;
  }
}

/* VISTA BAJA: LA CABECERA RUEDA CON LAS FUNCIONES. La tarjeta se ajusta al alto de la vista y, con la cabecera (anillo,
   franja de grupos, búsqueda) quieta encima, en un portátil con zoom a la rejilla le quedaban dos filas. Por debajo de
   900 px de alto rueda la lectura entera: la cabecera se va al bajar y la de columnas y la de cada módulo se quedan
   fijas, ahora contra `.bf-catread-main`. EXCEPCIÓN A MF-2 anotada: es una consulta de ALTO, que el «móvil primero» no
   ensancha (como la de `.bf-nf-art`); el `min-width` es el corte compacto de MF-3, por debajo la página ya rueda. */
@media (min-width: 620px) and (max-height: 900px) {
  .bf-catread-main {
    overflow-y: auto;
    scrollbar-width: thin;
  }

  .bf-catread-scroll {
    flex: none;
    overflow: visible;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-catread-roles,
  .bf-catread-tiles,
  .bf-catread-cols {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-catread-roles, .bf-catread-tiles, .bf-catread-cols) {
  background-color: var(--bf-card-bg);
}

@media (prefers-reduced-transparency: reduce) {
  .bf-catread-role.on {
    background: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] .bf-catread-role.on {
  background: var(--bf-card-bg);
}

/* ═══ fin del bloque de la chore 90.987 ═══ */

/* ═══ USUARIOS: LA BANDA DE FILTROS, SUELTA DE LAS TARJETAS Y SIN EMPUJAR (chores 90.803–90.805) ═══════════════════
   Petición del usuario, tres cosas de las dos secciones del módulo:
     · 90.803 — el conmutador Personas · Asignaciones (`.bf-usections`) lleva la GOTA como el resto de conmutadores:
       se añadió a las listas `:is(…)` de la gota (90.771/90.786/90.797); aquí no hay nada más que poner.
     · 90.804 — en Asignaciones con tarjetas, la banda iba pegada a la retícula: ahora, con el aire de la pila.
     · 90.805 — abrir los filtros no empujaba: el cuerpo colgaba de la banda como un flotante casi macizo. RETIRADO el
       2026-10-08 a petición del usuario («los menús de filtros en las tablas … deben tener transparencia y empujar el
       contenido de la tabla, pero el final de la tabla debe estar en la parte baja de la vista sin provocar scroll»):
       Usuarios vuelve a la banda de todas las listas —el cuerpo en el flujo, sobre el mismo cristal que la tabla, la
       empuja y la tabla encoge y rueda por dentro con su pie abajo, como en Sesiones—. */
.bf-users > .bf-toolbar:has(> .bf-filterbox) + .bf-hgrid {
  margin-top: 1rem;
}

/* 90.806 — el conmutador Personas · Asignaciones sacaba scroll horizontal: es un `.bf-tabs`, que desborda en `auto`
   para las fichas con muchas pestañas, y la gota al viajar se estira y el rótulo puesto crece un 5 %. Con dos opciones
   no hay nada que desplazar: sin desborde. Y más relleno, a petición del usuario: más alto y más ancho por opción, y
   aire entre el carril y la línea que dice para qué es la sección. La gota (90.797) entra con `.bf-page-header .acts >
   .bf-tabs` en su lista, que pesa tres clases: aquí se iguala para ganarle por orden. */
.bf-tabs.bf-usections.bf-usections {
  overflow: visible;
  /* El carril de las SECCIONES es más alto que el de control (1,75 rem): es navegación de módulo, no un filtro. */
  height: 2.25rem;
  margin-bottom: 0.125rem;
  padding: 4px;
  gap: 4px;
}

.bf-tabs.bf-usections.bf-usections > a {
  padding-inline: 1.25rem;
}

.bf-tabs.bf-usections.bf-usections > .bf-tabs-ink {
  top: 4px;
  bottom: 4px;
}

/* ═══ fin del bloque de las chores 90.803–90.806 ═══ */

/* ═══ LOS MODALES, CON EL CRISTAL DEL CENTRO DE CONTROL (chore 90.809) ═══════════════════════════════════════════
   Petición del usuario: los modales con la misma transparencia y el mismo color de fondo que el centro de control. El
   cristal del modal YA es el suyo (--bf-modal-tint = --bf-head-glass, el mismo valor que --bf-chrome-glass del shell,
   con el mismo filtro), pero el velo de detrás —un tinte pizarra al 34 %— apagaba la página, y el cristal, que toma su
   color de lo que tiene detrás, se veía más denso y más oscuro. Ahora el velo es como el del menú global del shell
   (90.268): SIN TINTE, sólo los 2 px de desenfoque, y el cristal compone igual que el del centro de control. Sin
   desenfoque (menos transparencia, por las tres vías) vuelve el tinte: un velo transparente y nítido no separa nada. */
.bf-overlay .bf-scrim {
  background: transparent;
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-overlay .bf-scrim {
    background: var(--bf-scrim);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-overlay .bf-scrim {
    background: var(--bf-scrim);
  }
}

:root[data-reduce~="transparency"] .bf-overlay .bf-scrim {
  background: var(--bf-scrim);
}

/* ═══ fin del bloque de la chore 90.809 ═══ */

/* ═══ EL CTA, SIN CANTO NI LUZ DE SUELO (chore 90.811) ═══════════════════════════════════════════════════════════
   Petición del usuario: no le gusta el borde del CTA ni el «semi led» que asoma por abajo. El borde era el filete de
   1 px en el color PLANO del CTA alrededor del degradado de la 90.802 —se leía como un marco— más la línea de luz
   interior de arriba; el led, la caída de su propio color. Fuera los dos: el degradado llega al canto (el filete
   transparente y el fondo pintado desde el borde) y sin sombra ninguna, en reposo, al apuntar y al pulsar. El
   degradado leve se queda: es lo que el usuario pidió en la 90.802. */
.bf-btn.primary,
.bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]),
.bf-btn.primary:active:not(:disabled):not([aria-disabled="true"]) {
  border-color: transparent;
  background-origin: border-box;
  box-shadow: none;
}

/* ═══ fin del bloque de la chore 90.811 ═══ */

/* ═══ EL CANTO DE LAS TARJETAS, TRANSLÚCIDO COMO EL DEL BUSCADOR GLOBAL (chore 90.815) ═════════════════════════════
   Petición del usuario: el canto de las tarjetas se veía MACIZO y el del buscador global, con cierta transparencia.
   Medido: en oscuro ya eran el mismo (1 px de --bf-edge-line = --bf-chrome-line, mismos píxeles). En claro no: el del
   buscador va por dentro de su cristal casi blanco y compone a 224 sobre un fondo de 240 —un canto que deja ver—,
   mientras que el de la tarjeta, la misma tinta al 16 %, caía sobre su velo y componía a 215 sobre 242: una raya. En
   claro el canto de las tarjetas baja al 9 % para componer como el del buscador; en oscuro se queda como estaba.
   Sólo el canto de las piezas de tarjeta (contorno de `.bf-card` y compañía, anillo de las de ítem y la banda de
   filtros suelta): las rayas interiores siguen con --bf-edge-line. */
:root {
  --bf-card-edge: color-mix(in srgb, var(--bf-tint-warm) 9%, transparent);
}

:root[data-theme="dark"] {
  --bf-card-edge: var(--bf-edge-line);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bf-card-edge: var(--bf-edge-line);
  }
}

:is(.bf-card, .bf-iglass, .bf-pcard, .bf-kpi, .bf-sect, .bf-prodstrip > a),
.bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
  .bf-gen-facts > li),
.bf-toolbar:has(> .bf-filterbox):not(.bf-card > *) {
  outline-color: var(--bf-card-edge);
}

:is(.bf-hcard, .bf-home-card) {
  --bf-edge-layers: linear-gradient(var(--bf-card-edge), var(--bf-card-edge));
}

.bf-toolbar:has(> .bf-filterbox):not(.bf-card > *) + .bf-card.bf-card {
  border-color: var(--bf-card-edge);
}

/* ═══ fin del bloque de la chore 90.815 ═══ */

/* ═══ EL RELIEVE, DE UNA PIEZA Y CON LUZ CÁLIDA (petición del usuario, 2026-10-06) ════════════════════════════════
   «Al apuntar una tarjeta con cristal y 3D, el borde no se mueve, por detrás se mueve un rectángulo y queda muy raro.»
   Era la receta: no giraba la tarjeta, giraban por separado su vista previa, su banda y la capa del canto, cada una con
   su propia perspectiva y su propio centro de giro (la vista sobre el suyo, la banda y el canto sobre el de la
   tarjeta), así que al inclinarse se separaban y el fondo se leía como un rectángulo que se movía bajo un borde quieto.
   Ahora gira LA TARJETA entera —fondo, canto, luz y sombra en un solo plano— y las piezas de dentro conservan su
   paralaje (translate), que es lo que da el volumen. Y la luz —el reflejo diagonal, el brillo del cursor y el canto que
   se enciende— deja la plata por un blanco cálido que tira a naranja, en los dos temas. */
/* En claro, «muchísimo menos anaranjado» (2026-10-06): parte del blanco de antes con sólo el punto cálido del papel
   (#f3f2f0), para que el reflejo se funda con el tema en vez de teñirlo.
   Chore 90.891: «el reflejo del hover de las cards de cristal en oscuro no debe tener anaranjado»: el oscuro deja la
   luz cálida (255 206 168, canto 255 186 134) por la blanca, la misma de la receta de Hugin. */
.bf-home-card[data-relief],
.bf-hcard[data-relief] {
  --lit: var(--bf-relief-light-warm);
  --rim-c: var(--bf-relief-rim-warm);
  transform: var(--tilt, none);
  transform-origin: 50% 50%;
}

:root[data-theme="dark"] :is(.bf-home-card, .bf-hcard)[data-relief] {
  --lit: var(--bf-relief-light);
  --rim-c: var(--bf-relief-light);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-home-card, .bf-hcard)[data-relief] {
    --lit: var(--bf-relief-light);
    --rim-c: var(--bf-relief-light);
  }
}

.bf-home-card[data-relief] > :is(.card-preview, .card-body),
.bf-hcard[data-relief] > :is(.card-preview, .card-body),
.bf-home-card[data-relief]::before,
.bf-hcard[data-relief]::before,
.bf-home-card[data-relief]::after,
.bf-hcard[data-relief]::after {
  transform: none;
}

/* LA SOMBRA, SIN PLACA (2026-10-06: «al mover el cursor se ve un rectángulo por detrás extraño»). La sombra que sigue
   al giro (90.774) se desplazaba moviendo su capa con translate, y una box-shadow nunca se pinta bajo su propia caja:
   al correrse la capa, el hueco de esa caja asomaba bajo la tarjeta como una placa clara con su sombra alrededor. Ahora
   la capa queda cosida a la tarjeta y lo que se desplaza son los desfases de la sombra, que jamás se pinta bajo la
   tarjeta. Y el giro, un punto más hondo (2,6° con más perspectiva), para que el volumen se lea. */
.bf-home-card[data-relief]::before,
.bf-hcard[data-relief]::before {
  translate: none;
  box-shadow:
    calc(var(--dx, 0) * -2px) calc(var(--dy, 0) * -2px + 2px) 6px -3px color-mix(in srgb, var(--floor) 12%, transparent),
    calc(var(--dx, 0) * -10px) calc(var(--dy, 0) * -8px + 10px) 30px -12px color-mix(in srgb, var(--floor) calc(var(--drift-shade, 0.3) * 100%), transparent);
  transition: opacity 260ms var(--bf-ease-out), box-shadow var(--settle);
}

.bf-home-card[data-relief][data-moving]:hover,
.bf-hcard[data-relief][data-moving]:hover {
  --tilt: perspective(1100px) rotateX(calc(var(--dy, 0) * 2.6deg)) rotateY(calc(var(--dx, 0) * -2.6deg));
}

/* El canto de la cifra del Inicio, de la misma tinta cálida (ya giraba entera). */
.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief] { --rim-c: var(--bf-relief-rim-warm); }
:root[data-theme="dark"] .bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief] { --rim-c: var(--bf-relief-light); }

/* ═══ fin del bloque del relieve de una pieza ═══ */

/* ═══ EL TEXTO, NÍTIDO AL APUNTAR (chore 90.984, petición del usuario, 2026-10-07) ══════════════════════════════════
   «El hover de los card item cristal debe salir definido, no turbio.» El giro 3D de la tarjeta entera (`--tilt`) obligaba
   al navegador a pintarla como una imagen y proyectarla: el texto perdía su suavizado nítido y se reescalaba. Medido: con
   2,6° y 1100 px de perspectiva el contorno sólo se deforma 1,3 px, así que el relieve que se lee lo hacen la luz que
   sigue al puntero, el canto que se enciende, la sombra que se corre y el paralaje de las piezas. Se queda todo eso y se
   va el giro: comparadas en pantalla, mismas luz, canto, sombra y contorno, y la letra nítida. Vale para las tarjetas de
   Instancias, Productos e Inicio y para las cifras del Inicio. */
.bf-home-card[data-relief][data-moving]:hover,
.bf-hcard[data-relief][data-moving]:hover {
  --tilt: none;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-moving]:hover {
  transform: none;
}

/* ═══ fin del bloque del texto nítido ═══ */

/* ═══ EL CANTO DEL BUSCADOR, EN LO QUE FLOTA (petición del usuario, 2026-10-06) ═══════════════════════════════════
   «Ese borde IDÉNTICO del buscador global debe estar en todas las cards con efecto cristal, modales, dropdown, select,
   toggles…». En las TARJETAS se probó y se retiró el mismo día («el borde de las cards con cristal se ve fatal, dejarlo
   como estaban»): conservan su filete (90.764/90.815). Lo llevan los modales, los desplegables y selectores, el
   calendario, las pistas, los avisos emergentes, la barra de selección y los conmutadores (segmentados e interruptor).
   Es el anillo del buscador (shell, .mega-card::after): un degradado de 1 px que se enciende arriba a la izquierda, se
   apaga por los lados y recoge un reflejo abajo a la derecha (--bf-glass-rim, tokens.css, mismos tonos). Se pinta en el
   BORDE de cada pieza con background-clip: border-area: se recorta al área del borde (transparente, de 1 px), sigue el
   radio, no tiñe el interior y vale también en piezas que recortan o que ruedan (la lista de un selector). La última
   capa es `none`, así que el velo de cada pieza no cambia. Sustituye al filete o aro interior que llevaban. Sin
   border-area no aplica nada. :is() toma la especificidad del selector más fuerte de su lista: gana a los :hover y
   estados que reescriben el fondo con el atajo `background`. */
@supports (background-clip: border-area) {
  :is(.bf-more .pop, .bf-pick .pop, .bf-pick.float .pop, .bf-dp, .bf-toast, .bf-selbar, .bf-modal,
    .bf-overlay .bf-modal,
    .bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row,
    .bf-switch, input.bf-switch) {
    border: 1px solid transparent;
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }

  /* Lo que flota pierde el aro interior que repetía el canto; conserva su caída. */
  :is(.bf-more .pop, .bf-pick.float .pop, .bf-dp, .bf-toast) { box-shadow: var(--bf-glass-drop); }
  .bf-selbar.bf-selbar { box-shadow: 0 6px 16px -6px color-mix(in srgb, var(--bf-shadow-ink) 18%, transparent); }
  :is(.bf-modal, .bf-overlay .bf-modal) { box-shadow: none; }

  /* Los conmutadores: el carril de los segmentados (el borde nuevo se come un píxel de su relleno —3, 2 en el del
     centro de control, 4 en Usuarios—, así miden lo mismo) y el del interruptor (su pulgar se corre un píxel atrás
     para no moverse). */
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    padding: 2px;
    box-shadow: none;
  }

  .bf-seg.bf-seg-cc.bf-seg-cc.bf-seg-cc { padding: 1px; }
  .bf-tabs.bf-usections.bf-usections.bf-usections { padding: 3px; }
  :is(.bf-switch, input.bf-switch)::after { left: calc(0.125rem - 1px); }
}

/* ═══ fin del bloque del canto del buscador ═══ */

/* ═══ LA COLUMNA DE LA FICHA, QUIETA (petición del usuario, 2026-10-06) ═══════════════════════════════════════════════
   «En instancias lo de la izquierda debe estar sticky y hacerse scroll solo de la derecha», también en la pestaña Menú.
   La columna ya era `sticky`, pero el panel de la vista desborda a lo ancho (`overflow-x: auto`) y eso lo hacía su
   contenedor de rueda: se pegaba a un panel que nunca rueda y se iba con la página. Como la columna de Carpetas (chore
   90.690): el panel recorta sin ser rueda (`clip`) y la columna se pega bajo la cabecera del shell. Sin rodillo propio
   (el usuario no quiere barras ahí): si no cabe en la ventana, su final llega con el de la página. Por debajo de 900 px la columna va arriba y no se pega (regla de la 90.787). */
@media (min-width: 900px) {
  .bf-admin-main > .bf-panel:has(.bf-ific) {
    overflow-x: clip;
  }

  /* `position` otra vez: la regla común de los cantos (`:is(.bf-card, .bf-iglass, … .bf-prodstrip > a)`, que pesa
     (0,1,1) por el `> a` de su lista) le ponía `relative` a la columna y le ganaba a su `sticky`. */
  .bf-ific > .bf-iside {
    position: sticky;
    top: 4.5rem;
  }
}

/* La pestaña Menú dentro de la rejilla: su editor ocupa la columna de la derecha (un contenedor sin contención, para que
   su ventana de edición —fija— siga anclada a la vista y no al panel). */
.bf-ific > .bf-imenu {
  min-width: 0;
}

/* ═══ fin del bloque de la columna quieta ═══ */

/* ═══ EL ICONO DE LA INSTANCIA, EN BALDOSAS (petición del usuario, 2026-10-06) ════════════════════════════════════════
   «En Datos lo de los iconos debe ser visualmente más atractivo». La rejilla de botones de campo con el dibujo a un lado
   pasa al lenguaje de la Lanzadera, que es donde se verá: cada opción es una BALDOSA de cristal con el icono y su nombre
   debajo; al apuntar, la baldosa sube y se enciende; la elegida toma el tinte de marca con su aro y un halo, y el nombre
   en la tinta de seleccionado. «El del producto» encabeza la rejilla como una ficha ancha (baldosa y qué hereda). Sólo
   en la ficha y el alta (`[data-pane="icon"]` de su formulario): el editor de menú usa la misma rejilla con la suya.
   Sigue siendo un grupo de radios de verdad (teclado, lector, sin JavaScript): el foco se dibuja en la baldosa. */
.bf-form-row[data-pane="icon"] .bf-iconpick {
  grid-template-columns: repeat(auto-fill, minmax(5.75rem, 1fr));
  gap: 0.375rem;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label {
  flex-direction: column;
  justify-content: flex-start;
  gap: 0.5rem;
  min-height: 0;
  padding: 0.75rem 0.25rem 0.625rem;
  border: 0;
  border-radius: 0.9rem;
  background: transparent;
  color: var(--bf-muted);
  font-size: 0.75rem;
  font-weight: 500;
  text-align: center;
  transition: background-color 220ms var(--bf-ease-out), color 220ms var(--bf-ease-out);
}

.bf-form-row[data-pane="icon"] .bf-iconpick label > span {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* La baldosa: el icono en su cuadro de cristal, con el canto del cristal y la luz arriba. */
.bf-form-row[data-pane="icon"] .bf-iconpick label > :is(svg, .ini) {
  box-sizing: content-box;
  width: 1.25rem;
  height: 1.25rem;
  padding: 0.75rem;
  border: 0;
  border-radius: 0.85rem;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bf-relief-light) 9%, transparent), transparent 60%),
    color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line), 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 6%, transparent);
  color: var(--bf-surface-fg);
  font-size: 0.95rem;
  transition:
    transform 260ms cubic-bezier(0.3, 1.4, 0.5, 1),
    background-color 220ms var(--bf-ease-out),
    box-shadow 220ms var(--bf-ease-out),
    color 220ms var(--bf-ease-out);
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  color: var(--bf-surface-fg);
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:hover > :is(svg, .ini) {
  transform: translateY(-2px);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 22%, transparent), 0 6px 14px -8px color-mix(in srgb, var(--bf-shadow-ink) 35%, transparent);
}

/* El foco, en la baldosa (la etiqueta ya no tiene caja). */
.bf-form-row[data-pane="icon"] .bf-iconpick label:focus-within {
  outline: 0;
  box-shadow: none;
}

/* Sólo con teclado (al pulsar con el ratón el radio también recibe el foco, y el aro sobraba). */
.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:focus-visible) > :is(svg, .ini) {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

/* La elegida: tinte de marca, aro, halo y el nombre en la tinta de seleccionado. */
.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked) {
  background-color: transparent;
  color: var(--bf-selected-ink);
  font-weight: 650;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked) > :is(svg, .ini) {
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bf-relief-light) 14%, transparent), transparent 60%),
    color-mix(in srgb, var(--bf-accent) 18%, transparent);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 60%, transparent),
    0 8px 18px -8px color-mix(in srgb, var(--bf-accent) 70%, transparent);
  color: var(--bf-accent-text, var(--bf-accent));
}

/* «El del producto»: una ficha ancha a la cabeza, la baldosa a la izquierda y qué hereda a su lado. */
.bf-form-row[data-pane="icon"] .bf-iconpick label.inherit {
  flex-direction: row;
  align-items: center;
  gap: 0.875rem;
  margin-bottom: 0.375rem;
  padding: 0.625rem 0.75rem;
  font-size: 0.8125rem;
  text-align: left;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label.inherit > span {
  white-space: normal;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label.inherit small {
  font-size: 0.75rem;
  font-weight: 400;
}

.bf-form-row[data-pane="icon"] .bf-iconpick .ini {
  display: grid;
  place-items: center;
  font-weight: 650;
}

/* El error, en el aro de las baldosas. */
.bf-form-row[data-pane="icon"] .bf-iconpick.err label > :is(svg, .ini) {
  box-shadow: inset 0 0 0 1px var(--bf-danger);
}

@media (prefers-reduced-motion: reduce) {
  .bf-form-row[data-pane="icon"] .bf-iconpick label:hover > :is(svg, .ini) { transform: none; }
}

/* ═══ fin del bloque de las baldosas del icono ═══ */

/* ═══ LOS BOTONES DESTRUCTIVOS, CON OTRO ESTILO (petición del usuario, 2026-10-06) ════════════════════════════════════
   «El botón de retirar debe ser visualmente más atractivo y este tipo de botones de eliminar, etc., tener otro estilo».
   En reposo sigue TINTADO, no macizo (S-6: el rojo lleno es de estado), pero ya no es una caja con filete: es cristal
   rojo —un velo en degradado, más luz arriba— con su CANTO de cristal en rojo (el del buscador: vivo arriba a la
   izquierda, se apaga por los lados y recoge un reflejo abajo a la derecha) y una ceja de luz por dentro. Al apuntarlo se
   ENCIENDE: rojo pleno con la letra de sobre-rojo y un halo rojo debajo, para que se lea que ESE es el que destruye; la
   orden sigue pidiendo confirmación en el modal. Fuera: el rojo macizo de la confirmación (`.solid`, ya es el último
   paso) y las filas de los menús «···», que son lista y no botón. Sin border-area, el tintado de siempre. */
@supports (background-clip: border-area) {
  .bf-btn.danger:not(.solid):not(.bf-more .pop *) {
    --dz: var(--bf-danger);
    border: 1px solid transparent;
    background-color: transparent;
    background-image:
      linear-gradient(135deg, color-mix(in srgb, var(--dz) 80%, transparent) 0%, color-mix(in srgb, var(--dz) 30%, transparent) 30%,
        color-mix(in srgb, var(--dz) 22%, transparent) 64%, color-mix(in srgb, var(--dz) 55%, transparent) 100%),
      linear-gradient(180deg, color-mix(in srgb, var(--dz) 24%, transparent), color-mix(in srgb, var(--dz) 11%, transparent));
    background-origin: border-box;
    background-clip: border-area, padding-box;
    box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
    color: var(--bf-danger-text);
    font-weight: 600;
    transition:
      background-image 0s,
      color 200ms var(--bf-ease-out),
      box-shadow 240ms var(--bf-ease-out),
      transform 200ms var(--bf-ease-out);
  }

  /* Chore 90.890: «el hover sobre los botones de rojo debe ser más suave». Ya no salta al rojo macizo —un degradado no
     se funde, así que el cambio era de golpe—: el cristal se queda y por detrás del texto le entra un VELO rojo en
     degradado (el ::after de la 90.835, aquí rojo y no blanco) en 320 ms, con el canto un punto más vivo y un halo
     corto. El rojo macizo queda para pulsar y para la confirmación. */
  .bf-btn.danger:not(.solid):not(.bf-more .pop *) {
    --bf-btn-glow: color-mix(in srgb, var(--dz) 30%, transparent);
  }

  .bf-btn.danger:not(.solid):not(.bf-more .pop *)::after {
    background:
      radial-gradient(120% 150% at 50% -30%, var(--bf-btn-glow), transparent 70%),
      linear-gradient(180deg, color-mix(in srgb, var(--dz) 16%, transparent), color-mix(in srgb, var(--dz) 9%, transparent));
    transition: opacity 320ms var(--bf-ease-out);
  }

  .bf-btn.danger:not(.solid):not(.bf-more .pop *):is(:hover, :focus-visible):not(:disabled) {
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, var(--bf-relief-light) 14%, transparent),
      0 4px 14px -9px color-mix(in srgb, var(--bf-danger) 45%, transparent);
  }

  .bf-btn.danger:not(.solid):not(.bf-more .pop *):active:not(:disabled) {
    background-image:
      linear-gradient(var(--bf-danger-solid), var(--bf-danger-solid)),
      linear-gradient(color-mix(in srgb, var(--bf-shadow-ink) 10%, var(--bf-danger-solid)), color-mix(in srgb, var(--bf-shadow-ink) 10%, var(--bf-danger-solid)));
    box-shadow: inset 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 20%, transparent);
    transform: scale(0.97);
  }

  .bf-btn.danger:not(.solid):not(.bf-more .pop *):focus-visible {
    outline-color: var(--bf-danger);
  }
}

/* ═══ fin del bloque de los botones destructivos ═══ */

/* ═══ LOS PIES DE LOS MODALES Y EL CARRIL HASTA EL PIE (petición del usuario, 2026-10-06) ═════════════════════════════
   «Donde están los botones de los modales de aviso, que es un footer, un poco más de padding»: el pie de todos los
   modales del Host (avisos, confirmaciones, alta de instancia, paneles con carril) respira más alrededor de sus botones.
   «En nueva instancia el panel navigator no está llegando hasta el divider de abajo»: el cuerpo de los paneles con
   carril es un formulario y heredaba su hueco entre filas (1,125 rem) entre la columna y el pie, así que el filete del
   carril se cortaba antes del divisor. Sin hueco, el carril llega al pie; el aire lo pone el relleno de la columna. */
.bf-modal footer {
  padding: 0.75rem 1rem;
  gap: 0.625rem;
}

.bf-menued-panel .bf-menued-pbody.bf-menued-pbody {
  gap: 0;
}

.bf-menued-panel .bf-menued-panes {
  padding-bottom: 1.25rem;
}

/* ═══ fin del bloque de los pies de los modales ═══ */

/* ═══ EL CANTO DE LAS TARJETAS, MÁS DISIMULADO (petición del usuario, 2026-10-06) ═════════════════════════════════════
   «El borde de las cards de cristal hacerlo un poco más disimulado»: el filete de siempre (contorno o anillo de 1 px,
   90.764/90.815) con menos tinta, en claro del 9 al 6,5 % y en oscuro del 16 al 11 %. Lo toman también los grupos del
   formulario de la ficha (Datos), que iban con la tinta de las rayas (--bf-edge-line). Las rayas interiores no cambian. */
:root {
  --bf-card-edge: color-mix(in srgb, var(--bf-tint-warm) 6.5%, transparent);
}

:root[data-theme="dark"] {
  --bf-card-edge: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bf-card-edge: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
  }
}

.bf-ific .bf-ipane .bf-form-row {
  outline-color: var(--bf-card-edge);
}

/* ═══ fin del bloque del canto más disimulado ═══ */

/* ═══ RODAR DENTRO DE UN MODAL: FUNDIDO BAJO LA CABECERA Y ABAJO (petición del usuario, 2026-10-06) ═══════════════════
   «Lo que oculte el header de un modal con el scroll debe transparentarse un poco y luego desaparecer detrás del header,
   y fade en la parte inferior del scroll sobre lo que no se vea». El cuerpo que rueda (`.in` de los modales y la
   columna de los paneles con carril) lleva una MÁSCARA con dos fundidos de 1,5 rem: el de arriba nace al rodar —en
   reposo, arriba del todo, no apaga nada— y el de abajo se va al llegar al final. Los mueven dos animaciones ligadas al
   rodillo del propio cuerpo (scroll(self)), en el compositor y sin JavaScript; si el cuerpo no rueda, su línea de tiempo
   no existe y no hay máscara. Donde el navegador no conoce estas líneas de tiempo, el corte seco de siempre. */
@property --bf-fade-t {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

@property --bf-fade-b {
  syntax: "<length>";
  inherits: false;
  initial-value: 0px;
}

@supports (animation-timeline: scroll()) {
  :is(.bf-overlay .bf-modal .in, .bf-menued-panel .bf-menued-panes) {
    /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bf-fade-t), #000 calc(100% - var(--bf-fade-b)), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent 0, #000 var(--bf-fade-t), #000 calc(100% - var(--bf-fade-b)), transparent 100%);
    animation: bf-fade-top linear both, bf-fade-bottom linear both;
    animation-timeline: scroll(self block), scroll(self block);
    animation-range: 0 2.5rem, calc(100% - 2.5rem) 100%;
  }
}

@keyframes bf-fade-top {
  from { --bf-fade-t: 0px; }
  to { --bf-fade-t: 1.5rem; }
}

@keyframes bf-fade-bottom {
  from { --bf-fade-b: 1.5rem; }
  to { --bf-fade-b: 0px; }
}

/* ═══ fin del bloque del fundido al rodar en los modales ═══ */

/* ═══ LA CABECERA CON PISTA, BAJO LA DEL SHELL (2026-10-06) ═══════════════════════════════════════════════════════════
   Subía a `z-index: 20` para quedar por encima del árbol y la rejilla, que es el MISMO nivel que la cabecera pegada del
   shell (.bf-frame-top); como va después en el árbol, le ganaba: sus mandos («Tarjetas | Tabla», «Nueva instancia») se
   pintaban encima del desplegable de avisos y del centro de control, y al rodar pasaban por encima de la cabecera. Con
   10 sigue sobre el árbol y la rejilla y queda bajo el marco. */
.bf-page-header:has(> .ttl.bf-thead) {
  z-index: 10;
}

/* ═══ fin del bloque de la cabecera con pista ═══ */

/* ═══ LOS CONMUTADORES: CANTO DEFINIDO, MÁS AIRE Y UNA GOTA DE CRISTAL (petición del usuario, 2026-10-06) ══════════════
   «Los toggle deben tener un borde más definido, un poco más de distancia entre la pill de seleccionado y el borde del
   fondo y, cuando hace el efecto gota, éste debe estar más elaborado y no difuminar el texto al pasar de una opción a
   otra». Tres cosas:
   · EL CANTO: bajo el degradado del cristal (que se apaga por los lados) va un filete UNIFORME de 1 px, así el carril
     queda dibujado entero y el brillo de arriba a la izquierda sigue encima. Los dos, en el área del borde.
   · EL AIRE: la pastilla queda a 5 px del canto por los cuatro lados (6 en Usuarios, 4 en el compacto): el relleno del
     carril y el inserto de la gota crecen a la par y el carril sube lo justo para que las opciones midan lo mismo.
   · LA GOTA: deja de ser casi transparente y de desenfocar lo que pisa; es una pastilla de CRISTAL con cuerpo —un velo
     en degradado, más claro arriba—, su canto de cristal (el del buscador), la luz que entra por arriba, el grueso que
     oscurece abajo, una sombra de contacto y otra que la despega; un brillo especular arriba a la izquierda. Mientras
     viaja (`data-glide`) se eleva un punto. El texto ya no se amplía al llegar (escalado, se rasterizaba borroso) y el
     cambio de tinta es corto: pasa de una opción a otra nítido. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 95%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 66%, transparent);
  --gota-hi: var(--bf-relief-light);
  --gota-lo: color-mix(in srgb, var(--bf-tint-warm) 7%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-tint-warm) 16%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 70%, transparent);
}

:root[data-theme="dark"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 14%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --gota-hi: color-mix(in srgb, var(--bf-relief-light) 14%, transparent);
  --gota-lo: color-mix(in srgb, var(--bf-shadow-ink) 28%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 45%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    --gota-top: color-mix(in srgb, var(--bf-relief-light) 14%, transparent);
    --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --gota-hi: color-mix(in srgb, var(--bf-relief-light) 14%, transparent);
    --gota-lo: color-mix(in srgb, var(--bf-shadow-ink) 28%, transparent);
    --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 45%, transparent);
    --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
  }
}

/* El canto: el degradado del cristal sobre el filete uniforme. */
@supports (background-clip: border-area) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    background-image: var(--bf-glass-rim), linear-gradient(var(--bf-seg-line), var(--bf-seg-line)), none;
    background-clip: border-area, border-area, border-box;
  }
}

@supports not (background-clip: border-area) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    box-shadow: inset 0 0 0 1px var(--bf-seg-line);
  }
}

/* El aire: relleno e inserto de la gota a la par, y el alto que lo compensa. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  padding: 4px;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc), .bf-fchips > .row) > .bf-tabs-ink {
  top: 4px;
  bottom: 4px;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc, .bf-seg-big)),
.bf-fchips > .row.row {
  height: 2rem;
}

.bf-seg.bf-seg-cc.bf-seg-cc.bf-seg-cc.bf-seg-cc { padding: 3px; }
.bf-seg.bf-seg-cc.bf-seg-cc > .bf-tabs-ink { top: 3px; bottom: 3px; }

.bf-tabs.bf-usections.bf-usections.bf-usections.bf-usections {
  height: 2.5rem;
  padding: 5px;
}

.bf-tabs.bf-usections.bf-usections.bf-usections > .bf-tabs-ink {
  top: 5px;
  bottom: 5px;
}

/* MF-4: en táctil las opciones crecen hasta --bf-tap-size (44 px). Con el alto fijo de arriba se salían por abajo del
   contenedor y el texto quedaba pegado al borde inferior; aquí el contenedor crece con ellas. Va DESPUÉS de los altos
   fijos y con su misma especificidad para ganarles. */
@media (pointer: coarse) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg:not(.bf-seg-cc, .bf-seg-big)),
  .bf-fchips > .row.row,
  .bf-tabs.bf-usections.bf-usections.bf-usections.bf-usections {
    height: auto;
  }
}

/* La gota de cristal. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  background:
    radial-gradient(40% 80% at 24% 0%, var(--gota-spec-soft), transparent 70%),
    linear-gradient(180deg, var(--gota-top), var(--gota-bottom));
  -webkit-backdrop-filter: saturate(1.3) brightness(var(--gota-lift));
  backdrop-filter: saturate(1.3) brightness(var(--gota-lift));
  box-shadow:
    inset 0 1px 0 var(--gota-hi),
    inset 0 -1px 0 var(--gota-lo),
    0 1px 2px var(--gota-drop),
    0 6px 14px -8px var(--gota-drop);
  transition: box-shadow 240ms var(--bf-ease-out);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row)[data-glide] > .bf-tabs-ink {
  box-shadow:
    inset 0 1px 0 var(--gota-hi),
    inset 0 -1px 0 var(--gota-lo),
    0 2px 4px var(--gota-drop),
    0 10px 20px -8px var(--gota-drop);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink::before {
  background: var(--bf-glass-rim);
}

/* El texto, nítido: sin ampliación al asentarse la gota. La tinta —texto e icono— cambia con la duración y la curva de
   la gota (chore 90.974, petición del usuario: «igual de suave que la gota»); corta, de 160 ms, se encendía entera
   antes de que la gota, que arranca despacio, hubiera salido. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row):has(> .bf-tabs-ink):not([data-glide])
  > [data-gota-on] {
  transform: none;
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > :is(a, button, label, i),
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > :is(a, button, label, i) > svg {
  transition: color var(--gota-glide) var(--gota-spring), background-color var(--bf-dur-base) var(--bf-ease-out);
}

/* Transparencia reducida: la gota, maciza como antes. */
@media (prefers-reduced-transparency: reduce) {
  :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
  background: var(--bf-card-bg);
}

/* ═══ fin del bloque de los conmutadores ═══ */

/* ═══ EL ICONO DE LA INSTANCIA: BALDOSAS LISAS Y SELECCIONADO «ENCENDIDA» (petición del usuario, 2026-10-06) ══════════
   «El seleccionado de icono en Datos debe ser como el seleccionado del sidebar, y sus cuadrados de fondo no deben tener
   gradiente y ser más transparentes». Las baldosas pierden la luz en degradado y bajan de velo y de filete. La opción
   elegida viste el «AQUÍ ESTÁS» ENCENDIDA del lateral (shell, chore 90.724) con sus mismos números: el velo de la
   tarjeta, la luz tibia del acento que baja desde arriba (10 % en claro, 14 % en oscuro) y el canto —el filete con un
   destello cálido (#ffd9b0) y la luz del acento sobre el icono—, que se enciende en la elegida y se apaga en la que se
   deja; el icono, en el acento, y el nombre, en la tinta del texto. En «El del producto» la luz cae sobre su baldosa. */
.bf-form-row[data-pane="icon"] .bf-iconpick {
  --ip-lit: 10%;
}

:root[data-theme="dark"] .bf-form-row[data-pane="icon"] .bf-iconpick {
  --ip-lit: 14%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-form-row[data-pane="icon"] .bf-iconpick {
    --ip-lit: 14%;
  }
}

.bf-form-row[data-pane="icon"] .bf-iconpick label > :is(svg, .ini),
.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked) > :is(svg, .ini) {
  background: color-mix(in srgb, var(--bf-surface-fg) 3%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-edge-line) 65%, transparent);
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:hover > :is(svg, .ini) {
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line), 0 6px 14px -10px color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
}

.bf-form-row[data-pane="icon"] .bf-iconpick label::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(ellipse 2.6rem 0.9rem at var(--ip-gx, 50%) 0%, color-mix(in srgb, var(--bf-glint-warm) 92%, transparent) 0%,
      color-mix(in srgb, var(--bf-glint-warm) 25%, transparent) 45%, transparent 100%),
    radial-gradient(ellipse 9rem 1.4rem at var(--ip-gx, 50%) 0%, color-mix(in srgb, var(--bf-accent) 55%, transparent) 0%,
      color-mix(in srgb, var(--bf-accent) 16%, transparent) 45%, transparent 100%),
    linear-gradient(var(--bf-edge-line), var(--bf-edge-line));
  /* El #fff de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box exclude, linear-gradient(#fff 0 0);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label.inherit {
  --ip-gx: 2.125rem;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked) {
  background-color: var(--bf-card-veil);
  background-image: radial-gradient(9rem 1.9rem at var(--ip-gx, 50%) 0%,
    color-mix(in srgb, var(--bf-accent) var(--ip-lit), transparent) 0%, transparent 72%);
  background-repeat: no-repeat;
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked)::before {
  opacity: 1;
}

.bf-form-row[data-pane="icon"] .bf-iconpick label:has(input:checked) > :is(svg, .ini) {
  color: var(--bf-accent);
}

/* ═══ fin del bloque del seleccionado «Encendida» del icono ═══ */

/* ═══ LA BARRA DE LA PÁGINA, COMO LA DE SPOTIFY (petición del usuario, 2026-10-06) ═══════════════════════════════════
   «El scroll de la página debe ser algo transparente, sin blur, como el de Spotify». Sólo el rodillo del DOCUMENTO
   (`body::-webkit-scrollbar` es el de la ventana): un pulgar plano y recto de 12 px que llena su carril, translúcido
   —la tinta del texto al 30 %, al 50 % al apuntarlo y al 70 % mientras se arrastra—, sin borde que lo adelgace, sin
   radio y sobre pista transparente. Las barras de dentro (tablas, listas, desplegables) conservan la píldora de Hugin. */
body::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}

body::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 30%, transparent);
  background-clip: border-box;
  border: 0;
  border-radius: 0;
}

body::-webkit-scrollbar-thumb:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 50%, transparent);
}

body::-webkit-scrollbar-thumb:active {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 70%, transparent);
}

body::-webkit-scrollbar-track,
body::-webkit-scrollbar-corner {
  background: transparent;
}

/* ═══ fin del bloque de la barra de la página ═══ */

/* ═══ EL HALO AL APUNTAR, TAMBIÉN EN EL CTA (petición del usuario, 2026-10-06) ═══════════════════════════════════════
   «El led del hover de retirar debe ser más disimulado; agregar dicho led al hover de los CTA». El halo del destructivo
   baja (más corto, más estrecho y a la mitad de tinta: ver su bloque) y el CTA lo toma al apuntarlo o enfocarlo, en su
   color: una luz bajo el botón, sin canto ni caída en reposo (90.811 sigue valiendo). No mueve nada: es sombra. */
.bf-btn.primary.primary:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) {
  box-shadow: 0 4px 12px -7px color-mix(in srgb, var(--bf-cta) 60%, transparent);
}

/* ═══ fin del bloque del halo del CTA ═══ */

/* ═══ LAS OPCIONES DEL SELECT, CONCÉNTRICAS CON SU LISTA (petición del usuario, 2026-10-06) ══════════════════════════
   «El hover y el seleccionado de las opciones del select deben ser más redondeadas para coincidir con el border radius
   del select». La lista abierta (::picker(select)) va con el radio del cristal (1,1 rem) y 0,375 rem de relleno, y sus
   filas llevaban el de un campo (0,4 rem): el realce se veía como una caja de esquinas duras dentro de una cápsula.
   Ahora cada fila lleva el radio de la lista MENOS su relleno, así el realce de apuntar, de foco y de la elegida sigue
   la curva de la lista a la misma distancia. */
@supports (appearance: base-select) {
  html:root select:not([multiple]):not([size]) option,
  select.bf-input option,
  .bf-field select option {
    border-radius: calc(var(--bf-glass-radius) - 0.375rem);
  }
}

/* ═══ fin del bloque de las opciones del select ═══ */

/* ═══ LOS BOTONES SECUNDARIOS, DE CRISTAL (petición del usuario, 2026-10-06) ═════════════════════════════════════════
   «Lo mismo que el botón Retirar, el mismo estilo pero sin el rojo, para los botones secundarios, pero más disimulado,
   al igual que su hover». El secundario (`.bf-btn` sin `primary`, `danger` ni `quiet`) deja el velo liso por la receta
   del destructivo en neutro: un velo de cristal en degradado (más luz arriba), su CANTO de cristal (el del buscador,
   en el área del borde, que ya era transparente) y una ceja de luz por dentro. Al apuntarlo sólo sube un punto de luz y
   echa un halo leve de sombra; nada de relleno macizo. Las filas de los menús «···» no son botones de esto. Sin
   border-area, el velo de siempre. */
.bf-btn {
  --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 72%, transparent);
  --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 42%, transparent);
  --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 90%, transparent);
  --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 60%, transparent);
  --bf-b2-hi: color-mix(in srgb, var(--bf-relief-light) 85%, transparent);
  --bf-b2-glow: color-mix(in srgb, var(--bf-tint-warm) 16%, transparent);
}

:root[data-theme="dark"] .bf-btn {
  --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 8.5%, transparent);
  --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 3.5%, transparent);
  --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 13%, transparent);
  --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --bf-b2-hi: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
  --bf-b2-glow: color-mix(in srgb, var(--bf-shadow-ink) 50%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-btn {
    --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 8.5%, transparent);
    --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 3.5%, transparent);
    --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 13%, transparent);
    --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --bf-b2-hi: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
    --bf-b2-glow: color-mix(in srgb, var(--bf-shadow-ink) 50%, transparent);
  }
}

@supports (background-clip: border-area) {
  .bf-btn:not(.primary, .danger, .quiet):not(.bf-more .pop *) {
    background-color: transparent;
    background-image: var(--bf-glass-rim), linear-gradient(180deg, var(--bf-b2-top), var(--bf-b2-bottom));
    background-origin: border-box;
    background-clip: border-area, padding-box;
    box-shadow: inset 0 1px 0 var(--bf-b2-hi);
  }

  .bf-btn:not(.primary, .danger, .quiet):not(.bf-more .pop *):is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) {
    background-image: var(--bf-glass-rim), linear-gradient(180deg, var(--bf-b2-top-on), var(--bf-b2-bottom-on));
    box-shadow: inset 0 1px 0 var(--bf-b2-hi), 0 4px 12px -8px var(--bf-b2-glow);
  }

  .bf-btn:not(.primary, .danger, .quiet):not(.bf-more .pop *):active:not(:disabled):not([aria-disabled="true"]) {
    background-image: var(--bf-glass-rim), linear-gradient(180deg, var(--bf-b2-bottom), var(--bf-b2-bottom));
    box-shadow: inset 0 1px 2px var(--bf-b2-glow);
  }
}

/* ═══ fin del bloque de los botones secundarios ═══ */

/* ═══ LA PESTAÑA PUESTA DE LA FICHA, CON CANTO DE CRISTAL (petición del usuario, 2026-10-06) ════════════════════════
   «Al seleccionar una pestaña Datos, Licencia, Salud de instancia, el seleccionado debe ser con los bordes tipo
   cristal». La puesta de la columna de la ficha cambia su filete uniforme por el canto del cristal (el del buscador,
   en el área del borde). Todas las pestañas llevan ya el píxel de borde, transparente, y un píxel menos de relleno: al
   ponerse una no cambia de tamaño. */
.bf-ific .bf-ivtabs > a {
  border: 1px solid transparent;
  padding: calc(0.5rem - 1px) calc(0.625rem - 1px);
}

@supports (background-clip: border-area) {
  .bf-ific .bf-ivtabs > a:is(.on, [aria-current="page"]) {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
    box-shadow: none;
  }
}

/* ═══ fin del bloque de la pestaña puesta ═══ */

/* ═══ LA GOTA, MÁS DISIMULADA, Y SIN ARO AL PULSAR (chore 90.830) ═════════════════════════════════════════════════════
   «En los toggle el efecto 3D de la gota del seleccionado debe ser más disimulado y al clicar en uno no debe dejar un
   borde naranja». El volumen de la gota baja: el degradado se aplana, la ceja y el grueso se atenúan, las sombras se
   acortan y al viajar apenas se eleva. Y el aro de foco del segmento (`.bf-seg label:focus-within`) saltaba también al
   pulsar con el ratón, porque el radio de dentro recibe el foco: ahora sólo con teclado (`:focus-visible` del radio). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 88%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 74%, transparent);
  --gota-hi: color-mix(in srgb, var(--bf-relief-light) 70%, transparent);
  --gota-lo: color-mix(in srgb, var(--bf-tint-warm) 4%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-tint-warm) 10%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 35%, transparent);
}

:root[data-theme="dark"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 11%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
  --gota-hi: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
  --gota-lo: color-mix(in srgb, var(--bf-shadow-ink) 15%, transparent);
  --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    --gota-top: color-mix(in srgb, var(--bf-relief-light) 11%, transparent);
    --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
    --gota-hi: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
    --gota-lo: color-mix(in srgb, var(--bf-shadow-ink) 15%, transparent);
    --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
    --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
  }
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink {
  box-shadow:
    inset 0 1px 0 var(--gota-hi),
    inset 0 -1px 0 var(--gota-lo),
    0 1px 1.5px var(--gota-drop),
    0 3px 8px -6px var(--gota-drop);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row)[data-glide] > .bf-tabs-ink {
  box-shadow:
    inset 0 1px 0 var(--gota-hi),
    inset 0 -1px 0 var(--gota-lo),
    0 1px 2px var(--gota-drop),
    0 5px 10px -7px var(--gota-drop);
}

.bf-seg label:focus-within {
  outline: 0;
  box-shadow: none;
}

.bf-seg label:has(input:focus-visible) {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

/* ═══ fin del bloque de la chore 90.830 ═══ */

/* ═══ AJUSTES: LA SECCIÓN PUESTA, COMO LA PESTAÑA DE LA FICHA (chore 90.831) ═════════════════════════════════════════
   «El seleccionado en Ajustes como el de la columna izquierda de instancia». La sección puesta del índice deja el velo
   de seleccionado con su filete uniforme por lo de la pestaña puesta de la ficha (90.827): el velo de la tarjeta al
   apuntar y el CANTO de cristal del buscador en el área del borde, con el glifo en el acento. Todas las secciones
   reservan su píxel de borde (un píxel menos de relleno): al ponerse una no cambia de tamaño. */
.bf-set-link {
  border: 1px solid transparent;
  padding: calc(0.375rem - 1px) calc(0.625rem - 1px);
}

.bf-set-link[aria-current="page"],
.bf-pagebox:has(.bf-gen-head) .bf-set-link[aria-current="page"] {
  background-color: var(--bf-card-veil-hover);
  box-shadow: none;
}

@supports (background-clip: border-area) {
  .bf-set-link[aria-current="page"],
  .bf-pagebox:has(.bf-gen-head) .bf-set-link[aria-current="page"] {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }
}

/* ═══ fin del bloque de la chore 90.831 ═══ */

/* ═══ LOS INTERRUPTORES, DE CRISTAL (chore 90.832) ═══════════════════════════════════════════════════════════════════
   «Rediseñar los toggle sin icono en base a los cambios de los otros toggle, más elaborados y premium». El interruptor
   toma la receta de los conmutadores (90.821/90.830): el CARRIL es un velo translúcido con su canto de cristal (90.783,
   en el área del borde) y una sombra interior leve; ENCENDIDO, el relleno que crece desde la izquierda es el CTA en
   degradado con su ceja de luz y echa un halo cálido; el PULGAR es una perla clara —degradado, filo finísimo, sombra de
   contacto corta— en los dos temas, como la gota. Las medidas, el recorrido y el estirón no cambian. */
:is(.bf-switch, input.bf-switch) {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 12%, transparent);
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 12%, transparent);
  transition: box-shadow 0.3s var(--bf-ease-out), background-color 0.2s var(--bf-ease-out);
}

:is(.bf-switch.on, input.bf-switch:checked) {
  box-shadow: inset 0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 8%, transparent), 0 2px 10px -4px color-mix(in srgb, var(--bf-cta) 70%, transparent);
}

:is(.bf-switch, input.bf-switch)::before {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-cta) 82%, var(--bf-relief-light)), var(--bf-cta));
  box-shadow: inset 0 1px 0 color-mix(in srgb, var(--bf-relief-light) 30%, transparent);
}

:is(.bf-switch, input.bf-switch)::after,
:is(.bf-switch.on, input.bf-switch:checked)::after {
  /* El fondo del pulgar lo pone la chore 90.839, con el mismo selector, más abajo. */
  box-shadow:
    0 1px 2px color-mix(in srgb, var(--bf-shadow-ink) 22%, transparent),
    0 3px 8px -3px color-mix(in srgb, var(--bf-shadow-ink) 25%, transparent),
    inset 0 0 0 0.5px color-mix(in srgb, var(--bf-shadow-ink) 8%, transparent);
}

/* ═══ fin del bloque de la chore 90.832 ═══ */

/* ═══ AJUSTES: LA SECCIÓN PUESTA, SIN SUBRAYADO (chore 90.834) ═══════════════════════════════════════════════════════
   La sección del índice es un enlace y heredaba el subrayado de los enlaces al apuntarla o estando puesta. Es una pieza
   de navegación, no un enlace de texto: sin subrayado en ningún estado, también en su rótulo. */
.bf-set-side .bf-set-link,
.bf-set-side .bf-set-link:is(:hover, :focus-visible, [aria-current="page"]),
.bf-set-side .bf-set-link * {
  text-decoration: none;
}

/* ═══ fin del bloque de la chore 90.834 ═══ */

/* ═══ EL HOVER DE LOS BOTONES, EN SU FONDO (chore 90.835) ═════════════════════════════════════════════════════════════
   «El hover de un botón no debe desplazarlo hacia arriba; debe ser algo relacionado con el fondo que tiene, suave y
   premium». Fuera el levantamiento de 1 px (90.384) y el icono que se adelantaba: el botón se queda quieto. Lo que
   cambia es su FONDO: una luz que nace arriba, en el centro, y se funde hacia abajo, en una capa propia DETRÁS del
   texto (::after con z-index −1 sobre el fondo del botón, que es contexto de apilado), con fundido de 280 ms. En el
   CTA y el destructivo es blanca; en los secundarios y terciarios, un tinte tibio del acento. Pulsar sigue hundiendo. */
.bf-btn {
  position: relative;
  isolation: isolate;
  --bf-btn-glow: color-mix(in srgb, var(--bf-accent) 12%, transparent);
}

.bf-btn:is(.primary, .danger) {
  --bf-btn-glow: color-mix(in srgb, var(--bf-relief-light) 24%, transparent);
}

.bf-btn:not([aria-busy="true"])::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  pointer-events: none;
  background: radial-gradient(120% 150% at 50% -30%, var(--bf-btn-glow), transparent 65%);
  opacity: 0;
  transition: opacity 280ms var(--bf-ease-out);
}

.bf-btn:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"])::after {
  opacity: 1;
}

.bf-btn:hover:not(:disabled):not([aria-disabled="true"]),
.bf-btn:hover:not(:disabled):not([aria-disabled="true"]) > svg {
  transform: none;
}

/* ═══ fin del bloque de la chore 90.835 ═══ */

/* ═══ LOS INTERRUPTORES, CON EL 3D MÁS DISIMULADO (chore 90.839) ═════════════════════════════════════════════════════
   «El efecto 3D de los toggle sin icono debe ser más disimulado». Se aplana la receta de la 90.832: el carril pierde
   la sombra interior, el relleno encendido casi no tiene degradado ni ceja y no echa halo, y el pulgar es casi liso
   con una sombra mínima. Se queda el cristal del carril (su canto) y el gesto. */
:is(.bf-switch, input.bf-switch),
:is(.bf-switch.on, input.bf-switch:checked) {
  box-shadow: none;
}

:is(.bf-switch, input.bf-switch)::before {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-cta) 94%, var(--bf-relief-light)), var(--bf-cta));
  box-shadow: none;
}

:is(.bf-switch, input.bf-switch)::after,
:is(.bf-switch.on, input.bf-switch:checked)::after {
  background: linear-gradient(180deg, var(--bf-relief-light), var(--bf-knob-low));
  box-shadow: 0 1px 1.5px color-mix(in srgb, var(--bf-shadow-ink) 18%, transparent);
}

/* ═══ fin del bloque de la chore 90.839 ═══ */

/* ═══ AVISOS: LA TARJETA, PLANA Y SIN DEGRADADO (chore 90.837) ═══════════════════════════════════════════════════════
   «Me parecen feas las notificaciones en Avisos: mejorar su diseño y quitar el gradiente sobre ellas». La tarjeta del
   aviso pasa a la línea de la campana del shell (90.819): un velo LISO —algo más denso sin leer— con el canto de
   cristal del buscador en vez del filete o del filo del acento, más radio y más aire; la baldosa del tipo, lisa y
   teñida de su severidad, sin aro; el cuerpo, a un ancho de lectura (72 caracteres) en vez de cruzar la pantalla; y la
   procedencia, en voz baja. Sin leer lo siguen diciendo el LED de la baldosa, el título entero y el velo; al apuntar,
   sólo sube un punto el velo. */
.bf-notice,
.bf-pagebox:has(.bf-gen-head) .bf-notice {
  gap: 0.5rem 0.875rem;
  padding: 1rem 1.125rem;
  border: 1px solid transparent;
  border-radius: 0.9rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 2.5%, transparent);
  box-shadow: none;
}

.bf-notice[data-read="false"],
.bf-pagebox:has(.bf-gen-head) .bf-notice[data-read="false"] {
  background: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  box-shadow: none;
}

.bf-notice:hover,
.bf-pagebox:has(.bf-gen-head) .bf-notice:hover,
.bf-pagebox:has(.bf-gen-head) .bf-notice[data-read="false"]:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  box-shadow: none;
}

@supports (background-clip: border-area) {
  .bf-notice,
  .bf-pagebox:has(.bf-gen-head) .bf-notice,
  .bf-pagebox:has(.bf-gen-head) .bf-notice[data-read="false"] {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }
}

.bf-notice-tile,
.bf-notice[data-read="true"] .bf-notice-tile {
  box-shadow: none;
}

.bf-notice-body {
  max-width: 72ch;
  margin-top: 0.375rem;
}

.bf-notice-meta {
  margin-top: 0.625rem;
}

/* ═══ fin del bloque de la chore 90.837 ═══ */

/* ═══ MODALES EN CLARO: LOS CAMPOS, CON UN BORDE DISIMULADO (chore 90.838) ═════════════════════════════════════════
   «En los modales en modo claro los inputs deben ser algo más visibles, poniéndoles un borde disimulado». Los campos
   no llevan borde (--bf-field-edge transparente) y, en claro, su velo blanco sobre el cristal blanco del modal se
   perdía. Dentro de un modal, y sólo en claro, el campo gana un filete de la tinta cálida al 14 %; en oscuro se queda
   como estaba, que ya se distingue. Desde la chore 90.842, también dentro de las tarjetas de las vistas. */
:is(.bf-modal, .bf-overlay .bf-modal, .bf-card, .bf-iglass, .bf-sect, .bf-ific .bf-ipane .bf-form-row) {
  --bf-field-edge: color-mix(in srgb, var(--bf-tint-warm) 14%, transparent);
}

:root[data-theme="dark"] :is(.bf-modal, .bf-overlay .bf-modal, .bf-card, .bf-iglass, .bf-sect, .bf-ific .bf-ipane .bf-form-row) {
  --bf-field-edge: transparent;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-modal, .bf-overlay .bf-modal, .bf-card, .bf-iglass, .bf-sect, .bf-ific .bf-ipane .bf-form-row) {
    --bf-field-edge: transparent;
  }
}

/* Los desplegables y las áreas de texto tienen su propia regla de borde (la del select de Hugin): el mismo filete, sin
   pisar el del foco. */
html:root :is(.bf-modal, .bf-overlay .bf-modal, .bf-card, .bf-iglass, .bf-sect, .bf-ific .bf-ipane .bf-form-row) :is(select:not([multiple]):not([size]), textarea):not(:focus-visible):not(:hover) {
  border-color: var(--bf-field-edge);
}

/* ═══ fin del bloque de la chore 90.838 ═══ */

/* ═══ LA GOTA, MÁS TRANSPARENTE Y VISIBLE EN CLARO (chore 90.845) ═══════════════════════════════════════════════════
   «El efecto gota de los toggle debe ser más transparente y con el gradiente mucho más disimulado, notándose también en
   el modo claro». El cuerpo de la gota baja a un velo casi uniforme (en claro, blanco del 60 al 54 %; en oscuro, del 8
   al 6 %), y lo que la dibuja es su CANTO: el degradado del cristal sobre un filete uniforme fino, más una sombra de
   contacto mínima; lo de detrás se aclara un punto (más en claro, donde un velo blanco sobre el carril claro no se
   leía). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 60%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 54%, transparent);
  --gota-hi: color-mix(in srgb, var(--bf-relief-light) 55%, transparent);
  --gota-lo: transparent;
  --gota-drop: color-mix(in srgb, var(--bf-tint-warm) 12%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 20%, transparent);
  --gota-lift: 1.06;
  --gota-line: color-mix(in srgb, var(--bf-tint-warm) 16%, transparent);
}

:root[data-theme="dark"] :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
  --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --gota-hi: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --gota-lo: transparent;
  --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 25%, transparent);
  --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 3%, transparent);
  --gota-lift: 1.08;
  --gota-line: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
    --gota-top: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
    --gota-bottom: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --gota-hi: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --gota-lo: transparent;
    --gota-drop: color-mix(in srgb, var(--bf-shadow-ink) 25%, transparent);
    --gota-spec-soft: color-mix(in srgb, var(--bf-relief-light) 3%, transparent);
    --gota-lift: 1.08;
    --gota-line: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
  }
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink::before {
  background: var(--bf-glass-rim), linear-gradient(var(--gota-line), var(--gota-line));
}

/* ═══ fin del bloque de la chore 90.845 ═══ */

/* ═══ EL EXPEDIENTE DE UNA PERSONA, CON SU COLUMNA (chore 90.848) ═══════════════════════════════════════════════════
   Reutiliza la ficha de instancia (.bf-ific, 90.787): la columna, la navegación vertical, los hechos y el panel. Sólo
   lo suyo: la cara de la persona, un punto mayor, hace de baldosa; y el panel PIERDE la contención de contenedor
   cuando dentro va un cajón o una capa fija (Dispositivos), que si no quedaría atrapada en él en vez de en la ventana. */
.bf-uside .bf-idcard-face :is(.bf-av, bf-avatar) {
  --bf-av-size: 3rem;
  width: 3rem;
  height: 3rem;
  font-size: 1.05rem;
}

.bf-uside .who b {
  font-size: 1.05rem;
}

.bf-upane:has(bf-drawer, .bf-drawer, .bf-overlay, dialog) {
  container-type: normal;
}

/* ═══ fin del bloque de la chore 90.848 ═══ */

/* ═══ AUTENTICACIÓN DE TERCEROS: EL VACÍO, CENTRADO EN LA PÁGINA (chore 90.850) ═══════════════════════════════════════
   Sin ningún proveedor, «Todavía no hay autenticación de terceros» quedaba pegado arriba bajo la cabecera. Cuando el
   vacío es lo único de la lista, ocupa el alto que queda de la vista y se centra en él. */
[data-slice="idps"]:has(> .bf-empty:only-child) {
  display: grid;
  place-items: center;
  min-height: calc(100dvh - 16rem);
}

/* ═══ fin del bloque de la chore 90.850 ═══ */

/* ═══ LAS FILAS DE LAS TABLAS, CON UN APUNTADO TRABAJADO (chore 90.846) ═════════════════════════════════════════════
   «El hover de las filas de la tabla de Usuarios debe ser más elaborado, premium y consistente, con animaciones suaves».
   En toda .bf-table (la de Usuarios y las demás, para que sea el mismo gesto en la app): la fila no cambia de golpe a
   un velo plano; un velo cálido ENTRA deslizándose desde la izquierda (el acento muy tenue que se funde en la tinta del
   texto) mientras se enciende un filete del acento en su canto izquierdo, y el contenido de la primera celda se
   adelanta 2 px. Todo con la curva de salida de la casa, en 300 ms. Las filas seleccionadas conservan su realce. */
/* Chore 90.872 («más premium, sin el gradiente ni el borde izquierdo», en TODAS las tablas): fuera el velo que entraba
   deslizándose en degradado, el filete del acento a la izquierda y el empujón de la primera celda. La fila apuntada
   toma un velo LISO que entra con un fundido de 240 ms y un filete de luz finísimo arriba y abajo, como una lámina de
   cristal bajo la fila; el contenido no se mueve. Las filas seleccionadas conservan su realce. */
.bf-table tbody tr > td {
  transition: background-color 240ms var(--bf-ease-out), box-shadow 240ms var(--bf-ease-out);
}

.bf-table.bf-table tbody tr:not(.sel):is(:hover, :focus-within) {
  background: none;
}

.bf-table.bf-table tbody tr:not(.sel):is(:hover, :focus-within) > td {
  background: color-mix(in srgb, var(--bf-surface-fg) 4.5%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--bf-surface-fg) 6%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
}

/* La rejilla del catálogo de permisos (otra tabla, no .bf-table): el mismo gesto. */
.bf-catread-grid tbody tr > * {
  transition: background-color 240ms var(--bf-ease-out), box-shadow 240ms var(--bf-ease-out);
}

.bf-catread-grid tbody tr:hover > * {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 4.5%, transparent);
  box-shadow:
    inset 0 1px 0 color-mix(in srgb, var(--bf-surface-fg) 6%, transparent),
    inset 0 -1px 0 color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .bf-table tbody tr > td, .bf-catread-grid tbody tr > * { transition: none; }
}

/* ═══ fin del bloque de la chore 90.846 ═══ */

/* ═══ SALUD: EL CATÁLOGO Y SUS PERMISOS, MÁS CLAROS Y MÁS USABLES (chore 90.841) ════════════════════════════════════
   «Lo de los permisos en salud de instancia debe ser más premium, usable, entendible y atractivo». Sin cambiar qué se
   cuenta, cambia cómo se lee y cómo se usa:
   · EL MOSAICO deja las baldosas azules macizas: cada módulo es una baldosa de cristal plano con su nombre, su cuenta
     en grande y una BARRA con su parte del total; es un enlace que abre su módulo en el árbol (instances.ts).
   · UN BUSCADOR sobre el árbol filtra por clave o nombre y abre lo que coincide.
   · EL ÁRBOL: cada módulo es una fila de cristal con su galón que gira, el nombre, la clave en voz baja y una pastilla
     con la cuenta; al abrirse, sus permisos salen como una rejilla de fichas (clave monoespaciada arriba, nombre
     debajo) en vez de una lista de renglones; el menú, igual. El módulo al que se salta se ilumina un instante. */
.bf-icatalog .bf-imosaic {
  gap: 0.5rem;
}

.bf-icatalog .bf-imosaic > li {
  min-height: 0;
  padding: 0;
  background: none;
  box-shadow: none;
  gap: 0;
}

.bf-icatalog .bf-imosaic > li > a {
  position: relative;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: 0.75rem 0.5rem;
  height: 100%;
  min-height: 5rem;
  padding: 0.75rem 0.875rem 0.875rem;
  border: 1px solid transparent;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 3.5%, transparent);
  color: var(--bf-surface-fg);
  text-decoration: none;
  transition: background-color 220ms var(--bf-ease-out);
}

@supports (background-clip: border-area) {
  .bf-icatalog .bf-imosaic > li > a {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }
}

.bf-icatalog .bf-imosaic > li > a:hover {
  background-color: color-mix(in srgb, var(--bf-surface-fg) 6.5%, transparent);
}

.bf-icatalog .bf-imosaic > li > a > b {
  font-size: var(--bf-text-ui);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-icatalog .bf-imosaic > li > a > .n {
  font-size: 1.375rem;
  font-weight: 650;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--bf-surface-fg);
}

.bf-icatalog .bf-imosaic > li > a > .bar {
  grid-column: 1 / -1;
  align-self: end;
  height: 3px;
  border-radius: 3px;
  background:
    linear-gradient(90deg, var(--bf-accent), color-mix(in srgb, var(--bf-accent) 55%, transparent)) 0 0 / var(--share, 0%) 100% no-repeat,
    color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
}

/* El buscador */
.bf-icat-find {
  position: relative;
  display: flex;
  align-items: center;
  max-width: 22rem;
}

.bf-icat-find > svg {
  position: absolute;
  left: 0.7rem;
  width: 0.9rem;
  height: 0.9rem;
  color: var(--bf-muted);
  pointer-events: none;
}

.bf-icat-find > .bf-input {
  width: 100%;
  padding-left: 2.1rem;
}

.bf-icat-none {
  margin: 0;
}

/* El árbol */
.bf-icatalog .bf-tree-wrap {
  border: 0;
  background: none;
}

.bf-icatalog .bf-tree {
  gap: 0.375rem;
  overflow: visible;
}

.bf-icatalog .bf-tree-group {
  border: 1px solid transparent;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 2.5%, transparent);
  overflow: hidden;
}

@supports (background-clip: border-area) {
  .bf-icatalog .bf-tree-group {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }
}

.bf-icatalog .bf-tree-group > summary.bf-node.mod {
  gap: 0.75rem;
  padding: 0.7rem 0.9rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  list-style: none;
  transition: background-color 200ms var(--bf-ease-out);
}

.bf-icatalog .bf-tree-group > summary::-webkit-details-marker {
  display: none;
}

.bf-icatalog .bf-tree-group > summary.bf-node.mod:hover {
  background: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
}

/* El galón: un chevrón dibujado en lugar del triángulo de texto, que gira al abrir. */
.bf-icatalog .bf-tree-group > summary .caret {
  display: grid;
  place-items: center;
  width: 1.4rem;
  height: 1.4rem;
  border-radius: 999px;
  font-size: 0;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
}

.bf-icatalog .bf-tree-group > summary .caret::before {
  content: "";
  width: 0.38rem;
  height: 0.38rem;
  border-right: 1.6px solid var(--bf-muted);
  border-bottom: 1.6px solid var(--bf-muted);
  transform: rotate(-45deg);
  transition: transform 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

.bf-icatalog .bf-tree-group[open] > summary .caret::before {
  transform: rotate(45deg);
}

.bf-icatalog .bf-tree-group > summary .name {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.25rem 0.6rem;
  flex: 1;
  min-width: 0;
}

.bf-icatalog .bf-tree-group > summary .name > .key {
  font-family: var(--bf-font-mono);
  font-size: 0.72rem;
  font-weight: 400;
  color: var(--bf-muted);
}

.bf-icatalog .bf-tree-group > summary .count {
  flex: none;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-accent) 13%, transparent);
  color: var(--bf-accent-text, var(--bf-accent));
  font-size: 0.72rem;
  font-weight: 600;
}

/* Los permisos abiertos, en rejilla de fichas */
.bf-icatalog .bf-tree-group[open] {
  padding-bottom: 0.6rem;
}

.bf-icatalog .bf-tree-group[open] > summary {
  margin-bottom: 0.35rem;
}

.bf-icatalog .bf-tree-group > .bf-node:not(.mod) {
  --bf-node-pad: 0.75rem;
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15rem;
  width: calc((100% - 2.5rem) / 3);
  margin: 0 0 0.4rem 0.6rem;
  padding: 0.5rem 0.7rem;
  border: 0;
  border-radius: 0.6rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 3.5%, transparent);
  vertical-align: top;
}

.bf-icatalog .bf-tree-group > .bf-node:not(.mod) > .key {
  font-family: var(--bf-font-mono);
  font-size: 0.7rem;
  color: var(--bf-muted);
  overflow-wrap: anywhere;
}

.bf-icatalog .bf-tree-group > .bf-node:not(.mod) > .name {
  font-size: var(--bf-text-ui);
  color: var(--bf-surface-fg);
}

/* Consultas de contenedor (I-2). Cada función del mosaico necesita unos 16 rem: a 52 rem de pestaña caben tres por
   fila ((52 − 2,5) / 3 ≈ 16,5) y por debajo dos; a 34 rem caben dos ((34 − 1,9) / 2 ≈ 16) y por debajo una. */
@container bf-ipane (max-width: 52rem) {
  .bf-icatalog .bf-tree-group > .bf-node:not(.mod) { width: calc((100% - 1.9rem) / 2); }
}

@container bf-ipane (max-width: 34rem) {
  .bf-icatalog .bf-tree-group > .bf-node:not(.mod) { width: calc(100% - 1.2rem); }
}

/* El módulo al que se salta desde el mosaico se ilumina un instante. */
.bf-icatalog .bf-tree-group.is-flash {
  animation: bf-icat-flash 1.1s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes bf-icat-flash {
  0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--bf-accent) 0%, transparent); }
  25% { box-shadow: 0 0 0 3px color-mix(in srgb, var(--bf-accent) 35%, transparent); }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* ═══ fin del bloque de la chore 90.841 ═══ */

/* ═══ LICENCIA Y SALUD DE UNA INSTANCIA, MÁS ATRACTIVAS (chore 90.840) ══════════════════════════════════════════════
   «Mejorar visualmente la vista de licencia y de salud de una instancia». Sin tocar qué se cuenta ni el mosaico de
   celdas (90.787), el vestido gana cuidado y se alinea con lo plano y de cristal de la plataforma:
   · la cabecera de cada celda lleva su icono en una baldosa pequeña teñida del acento, y el rótulo en la tinta;
   · el anillo de la vigencia acaba en redondo y echa un halo suave de su color;
   · los meses son pastillas más suaves —la vigencia en un degradado sutil del verde, la gracia rayada como antes—, y
     el mes actual se marca con un aro del acento en vez del blanco;
   · la tira de las últimas 12 horas, barras redondeadas: las que respondieron en verde con su degradado;
   · los vacíos («sin topes», «ninguna declarada») dejan el borde discontinuo por un velo liso;
   · los bytes de la huella de la clave, fichas de cristal sin filete. */
.bf-ific :is(.bf-icell-h, .bf-icell-k) {
  gap: 0.6rem;
  font-size: var(--bf-text-ui);
  font-weight: 600;
  color: var(--bf-surface-fg);
}

.bf-ific :is(.bf-icell-h, .bf-icell-k) > svg {
  box-sizing: content-box;
  flex: none;
  width: 0.9rem;
  height: 0.9rem;
  padding: 0.38rem;
  border-radius: 0.55rem;
  background: color-mix(in srgb, var(--bf-accent) 12%, transparent);
  color: var(--bf-accent-text, var(--bf-accent));
}

.bf-ivig .ring .fg {
  stroke-linecap: round;
  filter: drop-shadow(0 0 5px rgb(var(--bf-isteel) / 0.45));
}

.bf-ivig.warn .ring .fg {
  filter: drop-shadow(0 0 5px color-mix(in srgb, var(--bf-warning) 45%, transparent));
}

.bf-imonths {
  gap: 0.3rem;
}

.bf-imonths > li > i {
  height: 1.6rem;
  border-radius: 0.5rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
}

.bf-imonths > li.valid > i {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-success) 34%, transparent), color-mix(in srgb, var(--bf-success) 22%, transparent));
}

.bf-imonths > li.now > i {
  box-shadow: 0 0 0 1.5px var(--bf-accent), 0 0 10px -2px color-mix(in srgb, var(--bf-accent) 55%, transparent);
}

.bf-iuptime {
  gap: 0.3rem;
}

.bf-iuptime > li {
  height: 1.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
}

.bf-iuptime > li.ok {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-success) 52%, transparent), color-mix(in srgb, var(--bf-success) 32%, transparent));
}

.bf-iuptime > li.bad {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-danger) 70%, transparent), color-mix(in srgb, var(--bf-danger) 48%, transparent));
}

.bf-ivoid {
  border: 0;
  border-radius: 0.75rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 3.5%, transparent);
}

.bf-ifp > i {
  padding: 0.22rem 0.5rem;
  border-radius: 0.45rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: none;
}

/* ═══ fin del bloque de la chore 90.840 ═══ */

/* ═══ LAS PREFERENCIAS DE VISTA DE ESTE NAVEGADOR (chore 90.843) ════════════════════════════════════════════════════
   Las de Hugin que Bifrost no tenía, puestas en <html> por ts/ui/view-prefs.ts desde la cookie `bifrost.view`
   (Ajustes · Apariencia). El movimiento y la transparencia reducidos ya tenían sus reglas (`data-reduce`).
   · TAMAÑO DEL TEXTO: la raíz crece y con ella todo lo medido en rem (también el shell).
   · CONTRASTE ALTO: la tinta apagada se acerca a la del texto, los filetes y el canto de los campos se marcan y las
     tarjetas pasan a su fondo macizo, sin velo que deje ver lo de detrás.
   · SUBRAYAR ENLACES: los enlaces del texto (párrafos, listas, celdas, ayudas y avisos) llevan siempre su subrayado.
   · FOCO MARCADO: el aro del foco de teclado, más grueso y separado. */
:root[data-text="112"] { font-size: calc(var(--bf-font-size) * 1.12); }
:root[data-text="125"] { font-size: calc(var(--bf-font-size) * 1.25); }
:root[data-text="140"] { font-size: calc(var(--bf-font-size) * 1.4); }
:root[data-text="160"] { font-size: calc(var(--bf-font-size) * 1.6); }
:root[data-text="200"] { font-size: calc(var(--bf-font-size) * 2); }

:root[data-contrast="high"] {
  --bf-muted: color-mix(in srgb, var(--bf-surface-fg) 84%, var(--bf-canvas));
  --bf-dim: color-mix(in srgb, var(--bf-surface-fg) 74%, var(--bf-canvas));
  --bf-edge-line: color-mix(in srgb, var(--bf-surface-fg) 34%, transparent);
  --bf-card-edge: color-mix(in srgb, var(--bf-surface-fg) 34%, transparent);
  --bf-field-edge: color-mix(in srgb, var(--bf-surface-fg) 42%, transparent);
  --bf-card-veil: var(--bf-card-bg);
  --bf-card-veil-hover: var(--bf-card-bg);
}

:root[data-contrast="high"] :is(.bf-modal, .bf-overlay .bf-modal, .bf-card, .bf-iglass, .bf-sect, .bf-ific .bf-ipane .bf-form-row) {
  --bf-field-edge: color-mix(in srgb, var(--bf-surface-fg) 42%, transparent);
}

:root[data-links="always"] :is(p, li, td, dd, .bf-hint, .bf-msg) a:not(.bf-btn, [class*="bf-tab"], .bf-back) {
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

:root[data-focus="strong"] :focus-visible {
  outline: 3px solid var(--bf-accent) !important;
  outline-offset: 3px !important;
}

/* ═══ fin del bloque de la chore 90.843 ═══ */

/* 90.841 · el galón del módulo abierto: la regla del árbol ya gira la caja 90°; aquí el giro lo hace sólo el dibujo. */
.bf-icatalog .bf-tree-group[open] > summary .caret {
  transform: none;
}

/* ═══ AUTENTICACIÓN DE TERCEROS: LA TARJETA DEL PROVEEDOR, MÁS CUIDADA (chore 90.851) ═════════════════════════════════
   «Un diseño más premium, más usable y visualmente atractivo de las cards de autenticación de terceros, y con un borde
   más disimulado». La tarjeta pierde el aro interior y su filete baja a la mitad; al apuntarla sólo sube el velo.
   · ARRIBA, la marca en una baldosa mayor con su luz, el nombre y la clave en voz baja; el estado a la derecha.
   · ASÍ SALE EN EL ACCESO: el botón que verá la gente, de cristal, en su bandeja.
   · LOS DATOS, en una rejilla compacta: el rótulo pequeño y en voz baja, el valor al lado; los largos (autoridad, client ID) se
     recortan con puntos suspensivos y el entero va al apuntarlos; los scopes, en fichas.
   · LOS MAPEOS, en su bloque; y el PIE, con las acciones, separado por un filete fino. */
.bf-idps.bf-idps > .bf-pcard {
  border-radius: 1rem;
  outline-color: color-mix(in srgb, var(--bf-card-edge) 50%, transparent);
  box-shadow: none;
}

.bf-idps.bf-idps > .bf-pcard .top {
  gap: 0.875rem;
  padding: 1.125rem 1.125rem 0.875rem;
}

.bf-idps.bf-idps > .bf-pcard h3 {
  font-size: 1rem;
  font-weight: 650;
}

.bf-idps.bf-idps > .bf-pcard .top .key {
  font-family: var(--bf-font-mono);
  font-size: 0.72rem;
  color: var(--bf-muted);
}

.bf-idps.bf-idps .bf-idps-brand > .mark {
  width: 2.75rem;
  height: 2.75rem;
  border-radius: 0.85rem;
  background:
    linear-gradient(180deg, color-mix(in srgb, var(--bf-relief-light) 8%, transparent), transparent 70%),
    color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
}

.bf-idps.bf-idps .bf-idps-preview {
  margin: 0 1.125rem 0.875rem;
  padding: 0.75rem;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 3%, transparent);
}

.bf-idps.bf-idps .bf-idps-btn {
  justify-content: center;
  height: 2.5rem;
  border: 1px solid transparent;
  border-radius: 0.7rem;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
}

@supports (background-clip: border-area) {
  .bf-idps.bf-idps .bf-idps-btn {
    background-image: var(--bf-glass-rim), none;
    background-origin: border-box;
    background-clip: border-area, border-box;
  }
}

.bf-idps.bf-idps > .bf-pcard .facts {
  padding: 0 1.125rem 0.875rem;
}

.bf-idps.bf-idps > .bf-pcard .facts .bf-dl {
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: 0.55rem 0.75rem;
  align-items: center;
  font-size: var(--bf-text-ui);
}

.bf-idps.bf-idps > .bf-pcard .facts .bf-dl > dt {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-idps.bf-idps > .bf-pcard .facts .bf-dl > dd {
  min-width: 0;
  margin: 0;
}

/* Los largos se recortan; el valor entero va en su title. */
.bf-idps.bf-idps > .bf-pcard .facts .bf-dl > dd.trunc {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-idps-scopes {
  display: flex !important;
  flex-direction: row !important;
  flex-wrap: wrap;
  gap: 0.3rem !important;
}

.bf-idps-scopes > span {
  padding: 0.12rem 0.5rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  font-family: var(--bf-font-mono);
  font-size: 0.7rem;
}

.bf-idps.bf-idps > .bf-pcard .maps {
  margin: 0 1.125rem 0.875rem;
  padding: 0.7rem 0.8rem;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--bf-surface-fg) 2.5%, transparent);
}

.bf-idps.bf-idps > .bf-pcard .foot {
  gap: 0.5rem;
  padding: 0.75rem 1.125rem;
  border-top-color: color-mix(in srgb, var(--bf-card-edge) 60%, transparent);
}

/* ═══ fin del bloque de la chore 90.851 ═══ */

/* ═══ AUTENTICACIÓN DE TERCEROS: LA TARJETA ORDENADA Y QUIETA (chore 90.857) ══════════════════════════════════════════
   «La card de autenticación de terceros debe estar mejor organizada y no tener hover, sólo sus botones». Tres apartados
   con la MISMA forma —rótulo en versalitas y contenido debajo— separados por el mismo filete que el pie: la conexión, los
   mapeos (con la cuenta en el rótulo) y cómo sale en el acceso. Sin bandejas de distinto tono, que eran lo que la hacía
   parecer desordenada. La tarjeta no cambia al apuntarla y el nombre ya no es enlace: sólo los botones responden. */
.bf-idps.bf-idps > .bf-pcard .top {
  padding-bottom: 1rem;
}

.bf-idps.bf-idps > .bf-pcard > .desc {
  margin: -0.25rem 0 0;
  padding: 0 1.125rem 1rem;
  color: var(--bf-warning-text, var(--bf-muted));
  font-size: var(--bf-text-ui);
  line-height: 1.45;
}

.bf-idps.bf-idps > .bf-pcard > .bf-idps-sec {
  display: flex;
  flex-direction: column;
  gap: 0;
  margin: 0;
  padding: 0.9rem 1.125rem 1rem;
  border-top: 1px solid color-mix(in srgb, var(--bf-card-edge) 60%, transparent);
  border-radius: 0;
  background: none;
}

.bf-idps.bf-idps > .bf-pcard > .bf-idps-sec > .cap {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin: 0 0 0.7rem;
  font-size: var(--bf-text-mini);
  font-weight: 650;
  letter-spacing: 0.06em;
  line-height: 1.2;
  text-transform: uppercase;
  color: var(--bf-dim);
}

/* La cuenta de los mapeos, en una ficha pequeña junto al rótulo. */
.bf-idps.bf-idps > .bf-pcard > .bf-idps-sec > .cap > .n {
  display: inline-grid;
  place-items: center;
  min-width: 1.3rem;
  height: 1.15rem;
  padding: 0 0.35rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  color: var(--bf-text);
  font-size: 0.68rem;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0;
}

.bf-idps.bf-idps > .bf-pcard > .maps > .bf-hint {
  margin: 0;
}

/* El botón de muestra no es un mando: ni cursor ni apuntado. */
.bf-idps.bf-idps .bf-idps-btn {
  cursor: default;
}

/* ═══ fin del bloque de la chore 90.857 ═══ */

/* ═══ EL INTERRUPTOR CON SU TEXTO AL LADO Y SIN ARO AL PULSARLO (chore 90.853) ════════════════════════════════════════
   «El toggle de exigir correo verificado: el texto debe salir justo al lado del toggle, y al pulsarlo y pasar a
   desactivado no debe tener el borde naranja». Dos causas comunes a todos los interruptores en fila de un formulario
   (también Superadministrador y Cuenta de servicio en la ficha de usuario): la regla de \`.bf-field\` ponía la fila en
   COLUMNA —el rótulo caía debajo del mando— y el aro de foco de campo (un filete del CTA por dentro) se quedaba al
   pulsar con el ratón. La fila vuelve a ser fila, con el mando centrado en el rótulo, y el aro sólo sale con teclado. */
.bf-field.bf-switch-row {
  flex-direction: row;
  align-items: flex-start;
  gap: 0.875rem;
}

.bf-field.bf-switch-row > input.bf-switch {
  flex: none;
  margin-top: 0.1rem;
}

.bf-field.bf-switch-row > div > label {
  font-weight: 600;
}

input.bf-switch.bf-switch:focus:not(:focus-visible) {
  outline: 0;
  box-shadow: none !important;
}

/* ═══ fin del bloque de la chore 90.853 ═══ */

/* ═══ EL CARRIL DE LOS MODALES: EL SELECCIONADO, COMO EL DEL LATERAL (chore 90.854) ══════════════════════════════════
   «El seleccionado del panel navigator del modal debe ser como el del sidebar». El apartado puesto deja el tinte
   insinuado por el «AQUÍ ESTÁS» ENCENDIDA del lateral (shell, chore 90.724), con sus números: el velo de la tarjeta, la
   luz tibia del acento que baja desde arriba (10 % en claro, 14 % en oscuro) y el canto —el filete con un destello
   cálido (#ffd9b0) y la luz del acento— centrados en el icono; plegado, en el centro; desplegado, sobre el icono.
   El canto se enciende en el puesto y se apaga en el que se deja. */
.bf-menued-pnav {
  --pn-lit: 10%;
}

:root[data-theme="dark"] .bf-menued-pnav {
  --pn-lit: 14%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-menued-pnav {
    --pn-lit: 14%;
  }
}

.bf-menued-pnav > .it {
  position: relative;
  --pn-gx: 50%;
}

.bf-menued-pnav[data-open="true"] > .it {
  --pn-gx: 0.875rem;
}

.bf-menued-pnav > .it::before {
  content: "";
  position: absolute;
  inset: 0;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background:
    radial-gradient(ellipse 2.2rem 0.8rem at var(--pn-gx) 0%, color-mix(in srgb, var(--bf-glint-warm) 92%, transparent) 0%,
      color-mix(in srgb, var(--bf-glint-warm) 25%, transparent) 45%, transparent 100%),
    radial-gradient(ellipse 7rem 1.2rem at var(--pn-gx) 0%, color-mix(in srgb, var(--bf-accent) 55%, transparent) 0%,
      color-mix(in srgb, var(--bf-accent) 16%, transparent) 45%, transparent 100%),
    linear-gradient(var(--bf-edge-line), var(--bf-edge-line));
  /* El #fff de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#fff 0 0) content-box exclude, linear-gradient(#fff 0 0);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.bf-menued-pnav > .it[data-on="true"] {
  background-color: var(--bf-card-veil);
  background-image: radial-gradient(7rem 1.6rem at var(--pn-gx) 0%,
    color-mix(in srgb, var(--bf-accent) var(--pn-lit), transparent) 0%, transparent 72%);
  background-repeat: no-repeat;
  color: var(--bf-surface-fg);
  font-weight: 600;
}

.bf-menued-pnav > .it[data-on="true"]::before {
  opacity: 1;
}

.bf-menued-pnav > .it[data-on="true"] > svg {
  color: var(--bf-accent);
}

/* ═══ fin del bloque de la chore 90.854 ═══ */

/* ═══ MODALES: EL CONTENIDO RUEDA POR DEBAJO DE UNA CABECERA DE CRISTAL (chore 90.855) ══════════════════════════════
   «Sigo sin ver que el scroll del modal pase por debajo del header con transparencia y blur». El fundido anterior
   apagaba el contenido ANTES de llegar a la cabecera; ahora pasa por DEBAJO de ella. La cabecera sale del flujo y flota
   arriba con el velo y el desenfoque de la cabecera al rodar (--bf-head-glass, --bf-head-filter) y su filete; el
   cuerpo que rueda empieza en el canto de arriba del modal con un relleno del alto de la cabecera, así el contenido
   arranca donde estaba y, al rodar, se ve pasar desenfocado a través del cristal. El modal es raíz de fondo (lleva su
   propio desenfoque), así que el de la cabecera sólo coge lo de dentro. El alto de la cabecera va fijado en rem (el
   relleno que lo compensa también), y el panel con carril crece lo que la cabecera deja de ocupar. Abajo se queda el
   fundido sobre lo que falta por ver; el de arriba se va, porque taparía lo que se ve tras el cristal. */
.bf-overlay .bf-modal {
  --bf-mh: 2.875rem;
}

.bf-overlay .bf-modal.bf-menued-panel {
  --bf-mh: 3.375rem;
}

.bf-overlay .bf-modal > header {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 4;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  height: var(--bf-mh);
  border-bottom: 1px solid color-mix(in srgb, var(--bf-edge-line) 80%, transparent);
  background: color-mix(in srgb, var(--bf-head-glass) 82%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(1.5);
  backdrop-filter: blur(14px) saturate(1.5);
}

/* El modal sencillo: su cuerpo arranca bajo la cabecera, que ahora no ocupa sitio. */
.bf-overlay .bf-modal:not(.bf-menued-panel) > .in {
  padding-top: calc(var(--bf-mh) + 1rem);
  scroll-padding-top: var(--bf-mh);
}

/* El panel con carril: la columna y el carril arrancan en el canto de arriba, con su relleno. */
.bf-overlay .bf-modal.bf-menued-panel .bf-menued-pmain {
  max-height: calc(min(34rem, 100dvh - 12rem) + var(--bf-mh));
}

.bf-overlay .bf-modal.bf-menued-panel .bf-menued-panes {
  padding-top: calc(var(--bf-mh) + 0.5rem);
  scroll-padding-top: var(--bf-mh);
}

.bf-overlay .bf-modal.bf-menued-panel .bf-menued-pnav {
  padding-top: calc(var(--bf-mh) + 0.875rem);
}

/* Sólo el fundido de abajo. */
@supports (animation-timeline: scroll()) {
  :is(.bf-overlay .bf-modal .in, .bf-menued-panel .bf-menued-panes) {
    /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - var(--bf-fade-b)), transparent 100%);
    mask-image: linear-gradient(to bottom, #000 calc(100% - var(--bf-fade-b)), transparent 100%);
    animation: bf-fade-bottom linear both;
    animation-timeline: scroll(self block);
    animation-range: calc(100% - 2.5rem) 100%;
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-overlay .bf-modal > header { background: var(--bf-card-bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

:root[data-reduce~="transparency"] .bf-overlay .bf-modal > header {
  background: var(--bf-card-bg);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ═══ fin del bloque de la chore 90.855 ═══ */

/* ═══ EL MANDO DE PLEGAR EL CARRIL, CON AIRE DEBAJO (chore 90.856) ═══════════════════════════════════════════════════
   «Plegar del panel navigator necesita un poco de margin bottom»: el mando quedaba pegado al divisor del pie desde que
   el carril llega hasta él (y el pie lleva más aire). Medio rem por debajo. */
.bf-menued-pnav > .tgl {
  margin-bottom: 0.5rem;
}

/* ═══ fin del bloque de la chore 90.856 ═══ */

/* ═══ EL PULGAR DEL INTERRUPTOR, COMO LA ELEGIDA DE LOS CONMUTADORES (chore 90.858) ═══════════════════════════════════
   «El círculo de un toggle sin iconos, los simples, debe tener el color del seleccionado de los otros toggle con textos
   e iconos, como los del centro de control». La perla blanca deja paso a la GOTA de la 90.845: el mismo velo en degradado
   (en claro, blanco del 60 al 54 %; en oscuro, del 8 al 6 %), su ceja, su filete fino y su sombra de contacto corta. El
   carril, el relleno del CTA, las medidas y el gesto no cambian. */
:is(.bf-switch, input.bf-switch) {
  --sw-g-top: var(--bf-switch-thumb-top);
  --sw-g-bot: var(--bf-switch-thumb-bottom);
  --sw-g-hi: var(--bf-switch-thumb-hi);
  --sw-g-line: var(--bf-switch-thumb-line);
  --sw-g-drop: var(--bf-switch-thumb-drop);
}

:is(.bf-switch, input.bf-switch)::after,
:is(.bf-switch.on, input.bf-switch:checked)::after {
  background: linear-gradient(180deg, var(--sw-g-top), var(--sw-g-bot));
  box-shadow:
    inset 0 1px 0 var(--sw-g-hi),
    inset 0 0 0 1px var(--sw-g-line),
    0 1px 1.5px var(--sw-g-drop);
}

/* ═══ fin del bloque de la chore 90.858 ═══ */

/* ═══ EL PULGAR ENCENDIDO, DEL MISMO GRIS QUE APAGADO (chore 90.886) ═════════════════════════════════════════════════
   «Encendido, el círculo debe ser del mismo gris que apagado, no anaranjado». La gota de la 90.858 es translúcida y,
   encendido, deja ver el relleno del CTA. Bajo la gota va ahora lo que enseña apagado —el velo del carril sobre la
   base de las tarjetas— en una capa opaca; en los dos estados, para que sean el mismo gris y el naranja no asome
   tampoco mientras el pulgar se desliza. El carril encendido sigue naranja. */
:is(.bf-switch, input.bf-switch)::after,
:is(.bf-switch.on, input.bf-switch:checked)::after {
  background:
    linear-gradient(180deg, var(--sw-g-top), var(--sw-g-bot)),
    linear-gradient(color-mix(in srgb, var(--bf-surface-fg) var(--sw-base-mix, 12%), transparent) 0 0),
    var(--bf-drop-base);
}

/* ═══ fin del bloque de la chore 90.886 ═══ */

/* ═══ EL PULGAR, UN POCO MÁS OSCURO DE NOCHE (2026-10-08) ═════════════════════════════════════════════════════════════
   «El círculo de los toggle en modo oscuro debe salir un poco más oscuro». La capa opaca de la 90.886 lleva un 12 % de la
   tinta del texto, que de noche es clara: el pulgar salía gris medio (#33373B) sobre un carril casi negro. De noche baja
   al 7 % y el brillo de la gota a 5–4 % (--bf-switch-thumb-top y -bottom de tokens.css): queda en #262A2D, con su filete y su sombra de contacto, que son los que lo
   separan del carril. En claro no cambia nada. */
:root[data-theme="dark"] :is(.bf-switch, input.bf-switch) {
  --sw-base-mix: 7%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-switch, input.bf-switch) {
    --sw-base-mix: 7%;
  }
}

/* Y más oscuro todavía, apagado y ENCENDIDO por igual («sin estar activados, el círculo en modo oscuro debe ser más
   oscuro»; «igual de oscuro en activado»): la base de las tarjetas oscurecida, sin tinta y apenas con brillo; queda por
   debajo del carril, que lo recorta, y sobre el naranja encendido. Igual en el centro de control del shell. */
:root[data-theme="dark"] :is(.bf-switch, input.bf-switch)::after {
  background: linear-gradient(180deg, color-mix(in srgb, var(--bf-relief-light) 3%, transparent), color-mix(in srgb, var(--bf-relief-light) 2%, transparent)), color-mix(in srgb, var(--bf-drop-base) 80%, var(--bf-shadow-ink));
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) :is(.bf-switch, input.bf-switch)::after {
    background: linear-gradient(180deg, color-mix(in srgb, var(--bf-relief-light) 3%, transparent), color-mix(in srgb, var(--bf-relief-light) 2%, transparent)), color-mix(in srgb, var(--bf-drop-base) 80%, var(--bf-shadow-ink));
  }
}

/* ═══ fin del bloque del pulgar oscuro ═══ */

/* ═══ MODALES: TRAS LA CABECERA DE CRISTAL, SIN BARRA, DESENFOCADO Y APAGÁNDOSE A SU MITAD (chore 90.861) ═════════════
   «Lo del header transparente del modal: no debe verse el scroll por detrás del header, debe estar blurreado lo que
   pase por detrás y al llegar a la mitad ir desapareciendo». Sobre la 90.855:
   · LA BARRA arranca bajo la cabecera: su carril lleva de margen arriba el alto de la cabecera (las barras de Chromium
     son `::-webkit-scrollbar`, ver la regla global; el alto va en rem porque el seudoelemento no lee `--bf-mh`).
   · EL CRISTAL, con el velo más fino y más desenfoque: lo que pasa por detrás se ve, pero desenfocado.
   · EL FUNDIDO: el cuerpo empieza a apagarse en el canto de abajo de la cabecera y a su MITAD ya no se ve. Es la
     máscara del contenedor que rueda, que no rueda con él; en reposo no tapa nada, porque el contenido arranca bajo la
     cabecera. Abajo sigue el fundido de lo que falta por ver. */
.bf-overlay .bf-modal:not(.bf-menued-panel) > .in::-webkit-scrollbar-track {
  margin-top: 2.875rem;
}

.bf-overlay .bf-modal.bf-menued-panel .bf-menued-panes::-webkit-scrollbar-track {
  margin-top: 3.375rem;
}

.bf-overlay .bf-modal > header {
  background: color-mix(in srgb, var(--bf-head-glass) 55%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
}

:is(.bf-overlay .bf-modal:not(.bf-menued-panel) > .in, .bf-overlay .bf-modal.bf-menued-panel .bf-menued-panes) {
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: linear-gradient(to bottom, transparent calc(var(--bf-mh) * 0.5), #000 var(--bf-mh));
  mask-image: linear-gradient(to bottom, transparent calc(var(--bf-mh) * 0.5), #000 var(--bf-mh));
}

@supports (animation-timeline: scroll()) {
  :is(.bf-overlay .bf-modal:not(.bf-menued-panel) > .in, .bf-overlay .bf-modal.bf-menued-panel .bf-menued-panes) {
    /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
    -webkit-mask-image: linear-gradient(to bottom, transparent calc(var(--bf-mh) * 0.5), #000 var(--bf-mh),
      #000 calc(100% - var(--bf-fade-b)), transparent 100%);
    mask-image: linear-gradient(to bottom, transparent calc(var(--bf-mh) * 0.5), #000 var(--bf-mh),
      #000 calc(100% - var(--bf-fade-b)), transparent 100%);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-overlay .bf-modal > header { background: var(--bf-card-bg); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

:root[data-reduce~="transparency"] .bf-overlay .bf-modal > header {
  background: var(--bf-card-bg);
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

/* ═══ fin del bloque de la chore 90.861 ═══ */

/* ═══ USUARIOS: PERSONAS | ASIGNACIONES SIN QUE PAREZCA UNA RECARGA (chore 90.862) ════════════════════════════════════
   «Al cambiar de Personas a Asignaciones no debe recargar toda la página». No recargaba (es la capa, ts/ui/land.ts),
   pero lo parecía: la región hacía su entrada un fotograma, el rastro del shell se apagaba y volvía, las tarjetas
   entraban en cascada desde vacío y la cabecera crecía 4 px (el segmentado de Asignaciones mide 32 y los botones de
   Personas 28), así que todo lo de debajo saltaba. La región de un cambio de vista no hace entrada, el rastro sólo se
   cambia si cambia, lo que cambia entra con un fundido corto y la cabecera del módulo mide lo mismo en las dos. */
bifrost-shell > [data-nav-region][data-nav-view] {
  animation: none;
}

.bf-page-header:has(+ .bf-usections) {
  min-height: 2rem;
}

/* ═══ fin del bloque de la chore 90.862 ═══ */

/* ═══ CONMUTADORES: EL CANTO, ALGO MÁS TRANSPARENTE EN OSCURO (chore 90.863) ═════════════════════════════════════════
   «Los bordes de toggles como el de Personas y Asignaciones deben ser un poco más transparentes en oscuro». El filete
   uniforme del carril baja del 22 al 14 % y el brillo del cristal que va encima (el de tokens.css, redefinido aquí
   porque el degradado se resuelve en la raíz), a dos tercios; la gota lo hereda. En claro no cambia. Los valores
   viven en tokens.css (--bf-seg-line, --bf-seg-rim). */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --bf-glass-rim: var(--bf-seg-rim);
}

/* ═══ fin del bloque de la chore 90.863 ═══ */

/* ═══ USUARIOS · ASIGNACIONES: LA TARJETA DE CADA PERSONA, COMO LA DE PRODUCTOS (chores 90.866 y 90.871) ═══════════════
   «La card de asignaciones no tiene que tener el texto centrado ni tanta cosa en la parte superior, ya que se está
   cortando; debe lucir como las cards de productos». La vista lleva sólo la baldosa —la de Productos: grande y de
   esquinas suaves— y el usuario en la ficha de arriba a la izquierda; el pie, a la izquierda, el nombre y los accesos
   como las cifras de Productos: fichas neutras con el punto del color del rol y el número de instancias. */
.bf-people > .bf-person .tile {
  width: 3.75rem;
  height: 3.75rem;
  border-radius: 1.1rem;
  font-size: var(--bf-text-lead, 1.125rem);
}

.bf-people > .bf-person > .pv > .chip {
  font-family: var(--bf-font-mono, ui-monospace, monospace);
  letter-spacing: 0.02em;
}

.bf-people > .bf-person .ft > :is(.bf-cell, .bf-hbadge) {
  --cell-dot: var(--bf-muted);
  gap: 0.3rem;
  min-width: 0;
  height: 1.125rem;
  padding: 0 0.4rem;
  border: 0;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 16%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-weight: 600;
}

.bf-people > .bf-person .ft > .bf-cell.r1 { --cell-dot: var(--bf-data-1); }
.bf-people > .bf-person .ft > .bf-cell.r2 { --cell-dot: var(--bf-data-2); }
.bf-people > .bf-person .ft > .bf-cell.r3 { --cell-dot: var(--bf-data-3); }

/* El punto del rol, delante del producto. */
.bf-people > .bf-person .ft > .bf-cell::before {
  content: "";
  flex: none;
  width: 0.375rem;
  height: 0.375rem;
  border-radius: 50%;
  background: var(--cell-dot);
}

.bf-people > .bf-person .ft > .bf-cell > b {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
}

.bf-people > .bf-person .ft > .none {
  color: var(--bf-dim);
}

/* ═══ fin del bloque de las chores 90.866 y 90.871 ═══ */

/* ═══ LICENCIAS, A TODO EL ALTO Y MÁS CUIDADA (chore 90.873) ══════════════════════════════════════════════════════════
   «Rediseño del módulo de licencias, más premium y que ocupe el alto de la vista; el borde del campo con la clave, más
   disimulado». La página de la clave es una columna que `fillToView` lleva al pie de la ventana (desde el corte ancho;
   en estrecho, flujo normal): la cabecera de la clave que firma arriba y, debajo, la rejilla que se estira hasta abajo.
   · LA PÚBLICA: el pozo del PEM sin el filete marcado —un velo apenas más hondo y un canto casi invisible—, la huella
     SHA-256 bajo un filete fino y el pie con la garantía, abajo del todo.
   · LAS JUBILADAS crecen hasta el final de la columna; sin ninguna, un vacío centrado con su baldosa. */
.bf-keypage {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.bf-keypage > .bf-card,
.bf-keypage .bf-keygrid .bf-card {
  margin: 0;
}

@media (min-width: 1100px) {
  .bf-keypage[data-fill] {
    min-height: var(--fill-h, auto);
  }

  .bf-keypage > .bf-keygrid {
    flex: 1 0 auto;
    align-items: stretch;
  }

  .bf-keypage .bf-keypub,
  .bf-keypage .bf-keyretired {
    display: flex;
    flex-direction: column;
  }

  .bf-keypage .bf-keypub > .in {
    flex: 1 0 auto;
  }

  .bf-keypage .bf-keyside > .bf-keyretired {
    flex: 1 0 auto;
  }

  .bf-keypage .bf-keyretired > .bf-keyempty {
    flex: 1;
  }
}

/* El pozo de la clave: sin el filete marcado del campo. */
.bf-keypage .bf-keypub .bf-cred {
  padding: 0.75rem 0.875rem 0.875rem;
  border-color: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  border-radius: 0.9rem;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 3%, transparent);
  box-shadow: none;
}

.bf-keypage .bf-keypub .bf-cred:focus-within {
  border-color: color-mix(in srgb, var(--bf-surface-fg) 13%, transparent);
}

.bf-keypage .bf-keypub .bf-cred textarea {
  color: color-mix(in srgb, var(--bf-surface-fg) 78%, transparent);
}

/* La huella SHA-256, bajo un filete fino. */
.bf-keyprint {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-top: 0.875rem;
  border-top: 1px solid color-mix(in srgb, var(--bf-card-edge) 60%, transparent);
}

.bf-keyprint > .bf-label {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.bf-keyprint > .bf-label > svg {
  width: 0.9rem;
  height: 0.9rem;
  color: var(--bf-success);
}

.bf-keyprint > code {
  font-family: var(--bf-font-mono);
  font-size: var(--bf-text-mini);
  line-height: 1.7;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--bf-surface-fg) 82%, transparent);
  overflow-wrap: anywhere;
}

.bf-keyprint > .bf-hint {
  margin: 0;
}

/* Sin jubiladas: el vacío, centrado en lo que quede de la columna. */
.bf-keyempty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 1.5rem 1.25rem 2rem;
  text-align: center;
}

.bf-keyempty > .bf-htile {
  --hc-tone: var(--bf-muted);
  width: 3rem;
  height: 3rem;
  border-radius: 0.95rem;
}

.bf-keyempty > .bf-htile > svg {
  width: 1.35rem;
  height: 1.35rem;
}

.bf-keyempty > p {
  max-width: 18rem;
  margin: 0;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  line-height: 1.5;
}

/* ═══ fin del bloque de la chore 90.873 ═══ */

/* ═══ AVISOS: «EMITIDOS» CON LAS FICHAS DE LA BANDEJA (chore 90.875) ══════════════════════════════════════════════════
   «Emitir avisos» es ahora la sección «Emitidos» de Avisos: la misma cabecera, el carril Recibidos | Emitidos (las piezas
   de Personas | Asignaciones) y las mismas fichas que la bandeja (.bf-notice). Lo propio de un emitido: el cuerpo se
   resume en dos renglones —el entero está en el detalle—, el renglón de abajo añade PARA QUIÉN es y cuándo caduca, y el
   caducado baja de tono. El pie lleva la nota de la purga y el paginador a la derecha. */
.bf-notices-sent .bf-notice-body {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
}

.bf-notice-to,
.bf-notice-exp {
  font-size: var(--bf-text-mini);
  color: var(--bf-muted);
}

.bf-notice-to {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.4rem;
}

.bf-notice-to > b {
  font-weight: 600;
  color: color-mix(in srgb, var(--bf-surface-fg) 82%, transparent);
}

.bf-notice-to > b + b::before {
  content: "·";
  margin-right: 0.4rem;
  font-weight: 400;
  color: var(--bf-dim);
}

.bf-notices-sent .bf-notice[data-expired="true"] {
  opacity: 0.72;
}

.bf-notices-sent .bf-notices-card > .bf-foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.bf-notices-sent .bf-notices-card > .bf-foot > nav {
  margin-left: auto;
}

/* El carril, a la MISMA distancia de la cabecera en las dos secciones: en la bandeja la página es una columna sin hueco y
   en la administración el panel separa a sus hijos 14 px; sin esto, al cambiar de sección el carril saltaba. */
.bf-notices-head + .bf-nsections.bf-nsections {
  /* Chore 90.880: de 0,875 a 1,375 rem; pegado a las cápsulas de las cifras se leía como una fila más de ellas. */
  margin-top: 1.375rem;
}

/* Chore 90.883: «Emitidos» llega al pie de la vista (fillToView en ts/notifications/notifications.ts) y la banda de
   filtros va SOLDADA a «Emitidos por la instalación», sin hueco y con el mismo canto que la tarjeta. */
.bf-notices.bf-notices-sent {
  gap: 0;
}

@media (min-width: 620px) {
  .bf-notices-sent[data-fill] {
    min-height: var(--fill-h, auto);
  }
}

/* La banda llevaba su borde Y el contorno del canto (dos líneas, más clara que la tarjeta): sólo el contorno, el mismo
   tono que el borde de la tarjeta soldada debajo. */
.bf-notices-sent > .bf-toolbar.bf-toolbar {
  border-color: transparent;
}

/* ═══ fin del bloque de la chore 90.875 ═══ */

/* ═══ EL HOVER DEL CTA, MÁS SUAVE (chore 90.879) ══════════════════════════════════════════════════════════════════════
   «El hover de los CTA debe ser más suave». Las tres cosas que hacía a la vez, a la mitad: el degradado sólo se aclara
   hasta el punto medio entre su tono y el de apuntar (no hasta --bf-cta-hover), la luz que nace arriba baja de blanco al
   24 % al 12 %, y el halo de debajo queda al 35 % y más difuso. Y todo entra con un fundido más largo (360 ms). */
.bf-btn.primary {
  --bf-btn-glow: color-mix(in srgb, var(--bf-relief-light) 12%, transparent);
  transition:
    background-color 360ms var(--bf-ease-out),
    background-image 360ms var(--bf-ease-out),
    box-shadow 360ms var(--bf-ease-out),
    color 360ms var(--bf-ease-out);
}

.bf-btn.primary:hover:not(:disabled):not([aria-disabled="true"]) {
  background-image: linear-gradient(180deg,
    color-mix(in srgb, color-mix(in srgb, var(--bf-cta), var(--bf-cta-hover)), var(--bf-relief-light) 6%),
    color-mix(in srgb, var(--bf-cta), var(--bf-cta-hover)));
}

.bf-btn.primary.primary:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) {
  box-shadow: 0 5px 16px -9px color-mix(in srgb, var(--bf-cta) 35%, transparent);
}

.bf-btn.primary:not([aria-busy="true"])::after {
  transition: opacity 420ms var(--bf-ease-out);
}

/* ═══ fin del bloque de la chore 90.879 ═══ */

/* Ajustes · Apariencia: los mandos propios de la marca de la instalación (chore 90.881), al pie de su panel. */
.bf-set-panelacts {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  padding-top: 0.875rem;
}

/* ═══ «ACTIVIDAD RECIENTE» DEL INICIO, CON UN REALCE SUAVE (chore 90.887) ═══════════════════════════════════════════════
   «El hover de actividad reciente en inicio debe tener animación suave». La tinta se aviva en un fundido corto en vez de
   saltar, y el galón se adelanta un pelo al apuntar, como los enlaces de sección de Hugin. */
.bf-home-hero-link,
.bf-home-hero-link > .n {
  transition: color 0.24s var(--bf-ease-out);
}

.bf-home-hero-link > .caret {
  transition: transform 0.24s var(--bf-ease-out);
}

.bf-home-hero-link:hover > .caret {
  transform: translateX(2px);
}

.bf-home-hero-link:hover > .n {
  color: var(--bf-muted);
}

@media (prefers-reduced-motion: reduce) {
  .bf-home-hero-link,
  .bf-home-hero-link > .n,
  .bf-home-hero-link > .caret { transition: none; }
}

/* ═══ fin del bloque de la chore 90.887 ═══ */

/* ═══ LA AUDITORÍA REHECHA (chore 90.874) ═══════════════════════════════════════════════════════════════════════════════
   La maqueta aprobada (docs/mockups-vista/mockup-90874-auditoria.html): una tarjeta de cristal a todo el alto con la
   banda de filtros soldada arriba, los chips de lo puesto, el resumen plegable, la lista que rueda por dentro —por día
   con su cabecera fija, o por sesión— con las filas unidas por la línea de tiempo, y su pie. Las piezas de la casa que
   usa tal cual: .bf-card, .bf-seg (la gota), .bf-check, .bf-btn, .bf-tagk, .bf-pill, .bf-glass (los desplegables).
   Los colores de los verbos son de aquí: el sistema no tiene azul ni violeta propios. */
.bf-aud {
  --aud-line: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  --aud-veil: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  --aud-veil-on: color-mix(in srgb, var(--bf-surface-fg) 7%, transparent);
  --v-create: var(--bf-success);
  --v-update: var(--bf-aud-update);
  --v-delete: var(--bf-danger);
  --v-login: var(--bf-accent);
  --v-logout: var(--bf-muted);
  --v-deny: var(--bf-warning);
  --v-view: var(--bf-aud-view);
  --v-other: var(--bf-muted);
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  margin-top: 1rem;
}

.bf-aud-tabs { margin-top: 1.1rem; align-self: flex-start; }
.bf-aud-head > .ttl > .lead { max-width: none; }

.bf-aud-card {
  display: flex;
  flex-direction: column;
  min-height: 26rem;
  overflow: visible;
}

@media (min-width: 900px) {
  .bf-aud-card[data-fill] { height: var(--fill-h, auto); }
  .bf-aud-list { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; }
}

/* ── La banda ── */
.bf-aud-band {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.7rem 0.8rem;
  border-bottom: 1px solid var(--aud-line);
}

/* I-4 (2026-10-13): los mandos de la banda miden lo que todos los del sistema, --bf-control-height (S-11). */
.bf-aud-search,
.bf-aud-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  height: var(--bf-control-height);
  padding: 0 0.7rem;
  border: 0;
  border-radius: 0.6rem;
  background: var(--aud-veil);
  box-shadow: inset 0 0 0 1px var(--aud-line);
  color: var(--bf-muted);
  font: inherit;
  font-size: 0.8rem;
  transition: background-color 0.24s var(--bf-ease-out), box-shadow 0.24s var(--bf-ease-out), color 0.24s var(--bf-ease-out);
}

.bf-aud-search { flex: 1 1 15rem; max-width: 22rem; }
.bf-aud-search:focus-within { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 45%, transparent); }
.bf-aud-search > input { flex: 1; min-width: 0; height: 100%; border: 0; outline: 0; background: transparent; color: var(--bf-surface-fg); font: inherit; }
.bf-aud-search > input::placeholder { color: var(--bf-muted); }

/* El aro de foco lo pone la caja (focus-within), no el campo: el de la casa dibujaba un recuadro dentro de la cápsula. */
.bf-aud-search > input:focus-visible,
.bf-aud-popsearch > input:focus-visible { outline: none; box-shadow: none; }
.bf-aud-popsearch:focus-within { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 45%, transparent); }
.bf-aud-search > svg, .bf-aud-pill > svg { flex: none; width: 0.95rem; height: 0.95rem; }
.bf-aud-pill > svg:last-child { width: 0.8rem; height: 0.8rem; opacity: 0.7; }
.bf-aud-pill { cursor: pointer; white-space: nowrap; }
.bf-aud-pill:hover { background: var(--aud-veil-on); color: var(--bf-surface-fg); }
.bf-aud-pill > b { color: var(--bf-surface-fg); font-weight: 600; }
.bf-aud-pill.on { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 42%, transparent); }
.bf-aud-pill[aria-expanded="true"] { background: var(--aud-veil-on); }

.bf-aud-pill > b.n {
  display: inline-grid;
  place-items: center;
  min-width: 1.15rem;
  height: 1.15rem;
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--bf-cta);
  color: var(--bf-on-cta);
  font-size: 0.66rem;
  font-weight: 700;
}

.bf-aud-pick { position: relative; display: inline-flex; }

.bf-aud-end {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-left: auto;
}

.bf-aud-end .bf-check { font-size: 0.8rem; color: var(--bf-muted); white-space: nowrap; }
.bf-aud-mode > button > svg { width: 0.9rem; height: 0.9rem; }

/* ── Los desplegables (cristal de lo que flota) ── */
.bf-aud-pop {
  position: absolute;
  top: calc(100% + 0.45rem);
  left: 0;
  z-index: 30;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  width: 22rem;
  max-width: calc(100vw - 2rem);
  padding: 0.75rem;
  color: var(--bf-surface-fg);
  font-size: 0.8rem;
  animation: bf-aud-pop-in 0.22s var(--bf-ease-out) both;
}

@keyframes bf-aud-pop-in { from { opacity: 0; transform: translateY(-0.3rem) scale(0.98); } }

.bf-aud-pop.period { width: 25rem; }
.bf-aud-pop.ipbox { width: 17rem; }
.bf-aud-pop .cap { margin: 0; font-size: 0.66rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--bf-muted); }
.bf-aud-pop .grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.3rem; }

.bf-aud-pop .grid > button {
  padding: 0.4rem 0.2rem;
  border: 0;
  border-radius: 0.55rem;
  background: var(--aud-veil);
  color: var(--bf-muted);
  font: inherit;
  font-size: 0.76rem;
  cursor: pointer;
  transition: background-color 0.2s var(--bf-ease-out), color 0.2s var(--bf-ease-out);
}

.bf-aud-pop .grid > button:hover { background: var(--aud-veil-on); color: var(--bf-surface-fg); }

.bf-aud-pop .grid > button[aria-pressed="true"] {
  color: var(--bf-surface-fg);
  background: var(--bf-aud-pressed-bg);
  box-shadow: inset 0 0 0 1px var(--aud-line), 0 1px 1.5px var(--bf-aud-pressed-drop);
  font-weight: 600;
}

.bf-aud-pop .range { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.bf-aud-pop .range small { display: block; margin-bottom: 0.2rem; color: var(--bf-muted); font-size: 0.68rem; }
.bf-aud-pop .foot { display: flex; justify-content: flex-end; gap: 0.4rem; }
.bf-aud-pop .list { display: flex; flex-direction: column; gap: 0.1rem; max-height: min(20rem, 52vh); overflow-y: auto; margin: 0 -0.25rem; padding: 0 0.25rem; }
.bf-aud-pop .grp { display: flex; flex-direction: column; gap: 0.05rem; padding: 0.2rem 0; border-bottom: 1px solid var(--aud-line); }
.bf-aud-pop .grp:last-child { border-bottom: 0; }
.bf-aud-pop .none { margin: 0.5rem 0; color: var(--bf-muted); }

.bf-aud-opt {
  align-items: flex-start;
  padding: 0.3rem 0.4rem;
  border-radius: 0.45rem;
  transition: background-color 0.2s var(--bf-ease-out);
}

.bf-aud-opt:hover { background: var(--aud-veil); }
.bf-aud-opt.head { font-weight: 650; }
.bf-aud-opt:not(.head) { padding-left: 1.4rem; }
.bf-aud-opt .txt { display: flex; flex-direction: column; min-width: 0; }
.bf-aud-opt .txt small { overflow: hidden; color: var(--bf-muted); font-family: var(--bf-font-mono, ui-monospace, monospace); font-size: 0.68rem; font-weight: 400; text-overflow: ellipsis; white-space: nowrap; }

.bf-aud-popsearch {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  height: var(--bf-field-height);
  padding: 0 0.6rem;
  border-radius: 0.55rem;
  background: var(--aud-veil);
  box-shadow: inset 0 0 0 1px var(--aud-line);
  color: var(--bf-muted);
}

.bf-aud-popsearch > svg { width: 0.9rem; height: 0.9rem; flex: none; }
.bf-aud-popsearch > input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; color: var(--bf-surface-fg); font: inherit; }

.bf-aud-optbtn {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.5rem;
  border: 0;
  border-radius: 0.45rem;
  background: transparent;
  color: var(--bf-surface-fg);
  font: inherit;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.2s var(--bf-ease-out);
}

.bf-aud-optbtn:hover { background: var(--aud-veil); }
.bf-aud-optbtn > svg { width: 0.85rem; height: 0.85rem; flex: none; color: var(--bf-accent); opacity: 0; }
.bf-aud-optbtn[aria-pressed="true"] { font-weight: 600; }
.bf-aud-optbtn[aria-pressed="true"] > svg { opacity: 1; }

/* ── Los chips de lo puesto ── */
.bf-aud-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.55rem 0.9rem;
  border-bottom: 1px solid var(--aud-line);
}

.bf-aud-chips[hidden] { display: none; }

.bf-aud-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  height: 1.6rem;
  padding: 0 0.65rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-accent) 9%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 24%, transparent);
  font-size: 0.74rem;
}

.bf-aud-chip:has(> button) { padding-right: 0.25rem; }
.bf-aud-chip > em { color: var(--bf-muted); font-style: normal; }

.bf-aud-chip > button {
  display: grid;
  place-items: center;
  width: 1.15rem;
  height: 1.15rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--aud-veil-on);
  color: var(--bf-surface-fg);
  cursor: pointer;
  transition: background-color 0.2s var(--bf-ease-out);
}

.bf-aud-chip > button:hover { background: color-mix(in srgb, var(--bf-surface-fg) 14%, transparent); }
.bf-aud-chip > button > svg { width: 0.65rem; height: 0.65rem; stroke-width: 2.4; }

.bf-aud-chips .clear {
  margin-left: 0.25rem;
  padding: 0;
  border: 0;
  background: none;
  color: var(--bf-muted);
  font: inherit;
  font-size: 0.74rem;
  text-decoration: underline;
  text-underline-offset: 0.2em;
  cursor: pointer;
  transition: color 0.2s var(--bf-ease-out);
}

.bf-aud-chips .clear:hover { color: var(--bf-surface-fg); }

/* ── El resumen plegable ── */
.bf-aud-sumbar {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  /* I-4: la barra del resumen mide como una cabecera de tabla del sistema, no sus 40 px de antes. */
  min-height: var(--bf-table-header-height);
  padding: 0 1.1rem;
  border: 0;
  border-bottom: 1px solid var(--aud-line);
  background: none;
  color: var(--bf-surface-fg);
  font: inherit;
  font-size: 0.84rem;
  text-align: left;
  cursor: pointer;
  transition: background-color 0.24s var(--bf-ease-out);
}

.bf-aud-sumbar:hover { background: var(--aud-veil); }
.bf-aud-sumbar > svg { width: 0.95rem; height: 0.95rem; color: var(--bf-muted); }
.bf-aud-sumbar .lbl { font-weight: 600; }
.bf-aud-sumbar .mini { display: flex; flex-wrap: wrap; align-items: center; gap: 1rem; margin-left: auto; color: var(--bf-muted); font-size: 0.76rem; }
.bf-aud-sumbar .mini b { color: var(--bf-surface-fg); font-weight: 650; }
.bf-aud-sumbar .mini .warn, .bf-aud-sumbar .mini .warn b { color: var(--bf-accent-text, var(--bf-accent)); }
.bf-aud-sumbar .chev { display: grid; color: var(--bf-muted); transition: transform 0.25s var(--bf-ease-out); }
.bf-aud-sumbar .chev > svg { width: 0.85rem; height: 0.85rem; }
.bf-aud-sumbar.open .chev { transform: rotate(90deg); }
.bf-aud-sumbar.open .mini { visibility: hidden; }

.bf-aud .spark { display: inline-flex; align-items: flex-end; gap: 1px; height: 0.9rem; }
.bf-aud .spark > i { width: 3px; border-radius: 1px; background: color-mix(in srgb, var(--bf-surface-fg) 25%, transparent); }
.bf-aud .spark > i.peak, .bf-aud .bars > i.peak { background: var(--bf-accent); }

/* Mobile-first (MF-2): por debajo de 1100 el resumen va en tres columnas, con el pulso a todo el ancho y un filete bajo
   cada celda; a ancho completo, las siete cifras en una fila. */
.bf-aud-summary {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-bottom: 1px solid var(--aud-line);
}

.bf-aud-summary[hidden] { display: none; }
.bf-aud-summary .cell { padding: 0.75rem 0.9rem; border-right: 1px solid var(--aud-line); border-bottom: 1px solid var(--aud-line); }
.bf-aud-summary .cell.pulse { grid-column: 1 / -1; }
.bf-aud-summary .cell:nth-child(7) { border-right: 0; }
.bf-aud-summary .k { font-size: 0.66rem; font-weight: 650; letter-spacing: 0.06em; text-transform: uppercase; color: var(--bf-muted); }
.bf-aud-summary .v { margin-top: 0.3rem; font-size: 1.15rem; font-weight: 650; font-variant-numeric: tabular-nums; }
/* Una clave de acción larga o un rango con AM/PM (en inglés) parte la línea dentro de su celda, no pisa la de al lado. */
.bf-aud-summary .v.sm { font-size: 0.86rem; overflow-wrap: anywhere; }
.bf-aud-summary .v small { color: var(--bf-muted); font-size: 0.74rem; font-weight: 500; }
.bf-aud-summary .v.warn { color: var(--bf-accent-text, var(--bf-accent)); }
.bf-aud-summary .note { grid-column: 1 / -1; margin: 0; padding: 0.5rem 0.9rem; border-top: 1px solid var(--aud-line); color: var(--bf-muted); font-size: 0.74rem; }
.bf-aud .bars { display: flex; align-items: flex-end; gap: 2px; height: 2.1rem; margin-top: 0.4rem; }
.bf-aud .bars > i { flex: 1; min-width: 1px; border-radius: 2px 2px 0 0; background: color-mix(in srgb, var(--bf-surface-fg) 18%, transparent); }

@media (min-width: 1100px) {
  .bf-aud-summary { grid-template-columns: minmax(0, 1.3fr) repeat(6, minmax(0, 1fr)); }
  .bf-aud-summary .cell.pulse { grid-column: auto; }
  .bf-aud-summary .cell { border-bottom: 0; }
}

/* ── La lista ── */
.bf-aud-list { position: relative; }

.bf-aud-dayhead {
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.55rem 1.1rem;
  border-bottom: 1px solid var(--aud-line);
  background: color-mix(in srgb, var(--bf-card-bg) 84%, transparent);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  font-size: 0.76rem;
  font-weight: 650;
}

.bf-aud-dayhead .n { display: inline-grid; place-items: center; min-width: 1.3rem; height: 1.15rem; padding: 0 0.35rem; border-radius: 999px; background: var(--aud-veil-on); font-size: 0.66rem; }
.bf-aud-dayhead .when { color: var(--bf-muted); font-weight: 500; }

.bf-aud-tl { position: relative; padding: 0.35rem 0; }

.bf-aud-row {
  position: relative;
  display: grid;
  grid-template-columns: var(--bf-control-height) minmax(0, 1fr) auto auto auto;
  align-items: center;
  gap: 0.9rem;
  padding: 0.55rem 1.1rem;
  transition: background-color 0.24s var(--bf-ease-out);
}

.bf-aud-row:hover { background: var(--aud-veil); }

/* LA LÍNEA DE TIEMPO: pasa por el centro de los iconos y se suelda a sus cuadros (el cuadro es opaco y va encima). */
.bf-aud-tl > .bf-aud-row::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: calc(1.1rem + var(--bf-control-height) / 2 - 1px);
  width: 2px;
  background: color-mix(in srgb, var(--bf-surface-fg) 14%, transparent);
}

.bf-aud-tl > .bf-aud-row:first-child::before { top: 50%; }
.bf-aud-tl > .bf-aud-row:last-child::before { bottom: 50%; }
.bf-aud-tl > .bf-aud-row:only-child::before { display: none; }

.bf-aud-verb {
  --vc: var(--v-other);
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  /* I-4: el cuadro del verbo mide un mando del sistema (era de 2,1rem, 33 px). */
  width: var(--bf-control-height);
  height: var(--bf-control-height);
  border-radius: 0.65rem;
  background: linear-gradient(color-mix(in srgb, var(--vc) 15%, transparent), color-mix(in srgb, var(--vc) 15%, transparent)), var(--bf-card-bg);
  color: var(--vc);
}

.bf-aud-verb > svg { width: 0.95rem; height: 0.95rem; }
.bf-aud-verb.v-create { --vc: var(--v-create); }
.bf-aud-verb.v-update { --vc: var(--v-update); }
.bf-aud-verb.v-delete { --vc: var(--v-delete); }
.bf-aud-verb.v-login { --vc: var(--v-login); }
.bf-aud-verb.v-logout { --vc: var(--v-logout); }
.bf-aud-verb.v-deny { --vc: var(--v-deny); }
.bf-aud-verb.v-view { --vc: var(--v-view); }

.bf-aud-row .what { min-width: 0; }
.bf-aud-row .t { overflow: hidden; font-size: 0.86rem; text-overflow: ellipsis; white-space: nowrap; }
.bf-aud-row .t > b { font-weight: 650; }
.bf-aud-row .t q { font-weight: 600; quotes: "«" "»"; }
/* En inglés, las comillas de su idioma: la frase sale de los .resx en el idioma de quien mira. */
.bf-aud-row .t q:lang(en) { quotes: "“" "”"; }
.bf-aud-row .m { display: flex; flex-wrap: wrap; align-items: center; gap: 0.45rem; margin-top: 0.2rem; color: var(--bf-muted); font-size: 0.72rem; }
.bf-aud-row .m .bf-pill { font-size: 0.66rem; }
/* Una clave de producto larga («producto.cosa_larga.verbo») se corta con puntos suspensivos en vez de empujar el
   panel hacia la derecha a 375 px (MF-5). Entera sigue en la ficha. */
.bf-aud-row .m > .bf-tagk { min-width: 0; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bf-aud-row time { color: var(--bf-muted); font-size: 0.78rem; font-variant-numeric: tabular-nums; }
.bf-aud-row .ip { min-width: 5.5rem; color: var(--bf-muted); font-family: var(--bf-font-mono, ui-monospace, monospace); font-size: 0.72rem; text-align: right; }
.bf-aud-row .acts { display: flex; justify-content: flex-end; gap: 0.35rem; min-width: 6.2rem; }
.bf-aud mark { padding: 0 0.1rem; border-radius: 0.2rem; background: color-mix(in srgb, var(--bf-accent) 30%, transparent); color: inherit; }

/* ── Por sesión ── */
.bf-aud-sess {
  margin: 0.7rem 1rem;
  border-radius: 0.9rem;
  background: var(--aud-veil);
  box-shadow: inset 0 0 0 1px var(--aud-line);
  transition: box-shadow 0.24s var(--bf-ease-out);
}

.bf-aud-sess[open] { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 13%, transparent); }

.bf-aud-sess > summary {
  display: grid;
  grid-template-columns: 2.4rem minmax(0, 1fr) auto auto auto auto;
  align-items: center;
  gap: 0.9rem;
  padding: 0.75rem 0.9rem;
  border-radius: inherit;
  list-style: none;
  cursor: pointer;
  transition: background-color 0.24s var(--bf-ease-out);
}

.bf-aud-sess > summary::-webkit-details-marker { display: none; }
.bf-aud-sess > summary:hover { background: var(--aud-veil); }
.bf-aud-sess .who { display: flex; flex-direction: column; min-width: 0; }
.bf-aud-sess .who > b { font-size: 0.88rem; }
.bf-aud-sess .sub { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 0.2rem; color: var(--bf-muted); font-size: 0.72rem; }
.bf-aud-sess .sub .ip { font-family: var(--bf-font-mono, ui-monospace, monospace); }
.bf-aud-sess .mods { display: flex; gap: 0.3rem; }
.bf-aud-sess .count { color: var(--bf-muted); font-size: 0.76rem; white-space: nowrap; }
.bf-aud-sess .chev { display: grid; color: var(--bf-muted); transition: transform 0.25s var(--bf-ease-out); }
.bf-aud-sess .chev > svg { width: 0.85rem; height: 0.85rem; }
.bf-aud-sess[open] .chev { transform: rotate(90deg); }
.bf-aud-sess > .bf-aud-tl { border-top: 1px solid var(--aud-line); }
.bf-aud-sess .bf-aud-row .ip { display: none; }
.bf-aud-sess .bf-aud-row { grid-template-columns: var(--bf-control-height) minmax(0, 1fr) auto auto; }

.bf-aud-av {
  --hue: var(--v-update);
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 0.8rem;
  background: color-mix(in srgb, var(--hue) 16%, transparent);
  color: var(--hue);
  font-size: 0.8rem;
  font-weight: 700;
}

.bf-aud-av[data-hue="1"] { --hue: var(--v-create); }
.bf-aud-av[data-hue="2"] { --hue: var(--v-view); }
.bf-aud-av[data-hue="3"] { --hue: var(--bf-accent); }
.bf-aud-av[data-hue="4"] { --hue: var(--bf-aud-hue-pink); }
.bf-aud-av[data-hue="5"] { --hue: var(--bf-aud-hue-teal); }

/* ── El pie, el vacío y los huesos ── */
.bf-aud-foot {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 2.9rem;
  margin-top: auto;
  padding: 0.55rem 1.1rem;
  border-top: 1px solid var(--aud-line);
  color: var(--bf-muted);
  font-size: 0.76rem;
}

.bf-aud-foot > button { margin-left: auto; }
.bf-aud-sentinel { height: 1px; }

.bf-aud-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
  padding: 4rem 1rem;
  color: var(--bf-muted);
  text-align: center;
}

.bf-aud-empty > svg { width: 1.6rem; height: 1.6rem; margin-bottom: 0.4rem; opacity: 0.7; }
.bf-aud-empty > b { color: var(--bf-surface-fg); }
.bf-aud-skel .bf-aud-row > .bf-skel:nth-child(2) { width: 55%; height: 0.9rem; }
.bf-aud-skel .bf-aud-row > .bf-skel:nth-child(3) { width: 2.6rem; height: 0.8rem; }
.bf-aud-skel .bf-aud-verb { background: var(--aud-veil-on); }

/* ── La ficha (modal con navegador de apartados desde la chore 90.955; antes, cajón por la derecha) ── */
.bf-aud-detail .bf-menued-panel > header { align-items: flex-start; gap: 0.8rem; }
.bf-aud-detail .bf-menued-panel > header .bf-aud-verb { flex: none; width: 2.4rem; height: 2.4rem; }
.bf-aud-detail .bf-menued-panel > header .ttl { flex: 1; min-width: 0; }
.bf-aud-detail .bf-menued-panel > header h3 { margin: 0; font-size: 1rem; line-height: 1.3; }
.bf-aud-detail .bf-menued-panel > header p { margin: 0.25rem 0 0; color: var(--bf-muted); font-size: 0.76rem; }
.bf-aud-detail .bf-menued-pbody > footer { display: flex; gap: 0.5rem; }
.bf-aud-detail .bf-menued-pbody > footer:empty { display: none; }
/* Antes y después necesita aire: la comparación son dos columnas de JSON. */
.bf-overlay.bf-aud-detail .bf-modal.bf-menued-panel.has-rail { width: min(52rem, calc(100vw - 2rem)); }

/* ── En el teléfono: la tarjeta crece con la lista y la página rueda; la IP se va ── */
/* Excepción a MF-2: max-width porque la fila, la barra y las sesiones son las de escritorio, repartidas por todo el
   bloque de arriba, y en el teléfono solo se recolocan; escrita al revés habría que mover cada una a un min-width. */
@media (max-width: 899.98px) {
  .bf-aud-end { margin-left: 0; width: 100%; justify-content: space-between; }
  .bf-aud-search { max-width: none; flex-basis: 100%; }
  .bf-aud-row { grid-template-columns: var(--bf-control-height) minmax(0, 1fr) auto; }
  .bf-aud-row .ip { display: none; }
  .bf-aud-row .acts { grid-column: 2 / -1; justify-content: flex-start; min-width: 0; }
  .bf-aud-row .t { white-space: normal; }
  .bf-aud-sumbar .mini > :not(.spark):nth-child(n + 4) { display: none; }
  .bf-aud-sess > summary { grid-template-columns: 2.4rem minmax(0, 1fr) auto; }
  .bf-aud-sess .mods, .bf-aud-sess > summary > .bf-btn { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bf-aud-pop { animation: none; }
}

/* ═══ fin del bloque de la chore 90.874 ═══ */

/* ═══ EL PANORAMA, A TODO EL ALTO (chore 90.874) ════════════════════════════════════════════════════════════════════════
   La rejilla del panorama llega al pie de la vista (fillToView en overview.ts): sus dos columnas se estiran y la
   última tarjeta de cada una se queda con lo que sobra. La barra de filtros, sin caja, como la banda de «Actividad». */
.bf-aud-pano { margin-top: 1rem; }
.bf-aud-pano .bf-aud-panotools { flex-wrap: wrap; gap: 0.6rem 0.9rem; }
.bf-aud-pano .bf-aud-panotools .bf-check { font-size: 0.8rem; color: var(--bf-muted); }

@media (min-width: 900px) {
  .bf-aud-pano .bf-overview[data-fill] { min-height: var(--fill-h, auto); align-items: stretch; }
  .bf-aud-pano .bf-overview[data-fill] > .col > .bf-card:last-child { flex: 1 1 auto; }
}

/* ═══ fin del bloque de la chore 90.874 (panorama) ═══ */

/* ═══ EL FOCO DE CAMPOS Y BUSCADORES, MÁS DISIMULADO (chore 90.903) ════════════════════════════════════════════════════
   «El borde focus de los inputs y buscadores debería ser un poco más disimulado, tanto más fino como más transparente».
   El canto engordaba a dos píxeles (el borde más un inset de 1 px) y era la CTA plena: ahora un píxel, y la CTA al 55 %
   (--bf-field-focus, tokens.css). Los campos en error conservan su rojo, que va antes por especificidad. */
.bf-input.focus,
.bf-input:focus,
.bf-field :is(input, select, textarea):focus,
.bf-fsearch:focus-within,
.bf-field-search:focus-within {
  box-shadow: none;
}

input.bf-filter:focus-visible,
select.bf-filter:focus-visible {
  border-color: var(--bf-field-focus);
  box-shadow: none;
}

.bf-aud-search:focus-within,
.bf-aud-popsearch:focus-within {
  box-shadow: inset 0 0 0 1px var(--bf-field-focus);
}

/* ═══ fin del bloque de la chore 90.903 ═══ */

/* ═══ EL FOCO DE TODO CAMPO: UN SOLO CANTO, NARANJA Y FINO (chore 90.959) ══════════════════════════════════════════════
   «El focus de los inputs no debe tener dos bordes, sólo el naranja pero fino». La 90.903 lo dejó así en los campos de
   formulario, pero quedaban focos de dos capas: el canto MÁS un anillo de 1 px por dentro (filtros, el salto de página,
   el pegado) o MÁS un halo por fuera (los campos del código de verificación). Todos pasan al canto de 1 px en
   --bf-field-focus y nada más. Los buscadores de Auditoría no entran: no tienen borde y su canto ES ese anillo, una sola
   línea que la 90.903 ya pinta en naranja. Lo que está en error conserva su rojo (`:not([aria-invalid])`). */
.bf-filter:has(select:focus-visible),
.bf-paste:focus-within,
.bf-code i.on {
  border-color: var(--bf-field-focus);
  box-shadow: none;
}

.bf-filter select:focus-visible,
.bf-filterbox-body .bf-filter > select:focus-visible,
.bf-filterbox-body .bf-filterrow > select.bf-filter:focus-visible,
.bf-page .jump input:focus,
.bf-code input:focus:not([aria-invalid="true"]),
.bf-code-one input:focus:not([aria-invalid="true"]) {
  outline: none;
  border-color: var(--bf-field-focus);
  box-shadow: none;
}

/* ═══ fin del bloque de la chore 90.959 ═══ */

/* ═══ LOS CAMPOS NUMÉRICOS, CON CARETS (chore 90.904) ═══════════════════════════════════════════════════════════════════
   «Cambiar las flechas de los input tipo number a carets en esas direcciones». El mando del navegador se viste con los
   dos carets de la casa (--bf-number-carets, en la tinta de la flecha del desplegable): la mitad de arriba sube y la de
   abajo baja, como antes. Apagado hasta que se apunta o se enfoca el campo, como el nativo. */
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  appearance: none;
  width: 0.85rem;
  height: 1.5rem;
  margin: 0 -0.15rem 0 0.35rem;
  background: var(--bf-number-carets) center / 0.85rem 0.85rem no-repeat;
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.2s var(--bf-ease-out);
}

input[type="number"]:is(:hover, :focus)::-webkit-inner-spin-button { opacity: 0.75; }
input[type="number"]::-webkit-inner-spin-button:hover { opacity: 1; }
input[type="number"]::-webkit-outer-spin-button { display: none; }
.bf-page .jump input::-webkit-inner-spin-button { display: none; } /* el salto de página no los llevaba, a propósito */

/* ═══ fin del bloque de la chore 90.904 ═══ */

/* ═══ LOS MODALES: SOMBRA MUY SUAVE Y MÁS DESENFOQUE DETRÁS (chore 90.905) ═════════════════════════════════════════════
   «Debe haber una sombra muy suave tras los modales y un poco más de blur en el overlay backdrop». El velo sigue sin
   tinte (90.809) pero desenfoca 4 px en vez de 2, y el modal recupera una caída larga y tenue —ancha, muy difusa, sin
   canto—, cálida en claro y negra en oscuro. Corrección del usuario: la sombra, más disimulada, y un pelín menos de
   desenfoque (era 6 px). */
.bf-overlay .bf-scrim {
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
}

:is(.bf-modal, .bf-overlay .bf-modal).bf-modal {
  box-shadow: var(--bf-modal-soft-drop, none);
}

/* ═══ fin del bloque de la chore 90.905 ═══ */

/* ═══ LOS DESPLEGABLES, DEL COLOR DE LOS CAMPOS; LOS MARCADORES, DEL DE LA LUPA (chore 90.906) ═══════════════════════════
   «El color de los selectores debe coincidir con el de los input text y el color de los placeholder de los buscadores con
   el del icono de lupa». Los de las barras de filtro llevaban el opaco del control y la tinta apagada, y los campos de
   texto el velo del campo y la tinta: ahora los dos lo mismo (la lista ya es la de cristal propia, `base-select`, así
   que el velo no la toca). Y el marcador de cualquier buscador, en la tinta de su lupa (el de carpetas e instancias caía
   en el gris del navegador). */
select.bf-filter:not(.on),
.bf-filter:has(> select):not(.on) {
  background-color: var(--bf-field-bg);
  color: var(--bf-surface-fg);
}

input[type="search"]::placeholder,
.bf-fsearch > input::placeholder {
  color: var(--bf-muted);
  opacity: 1;
}

/* ═══ fin del bloque de la chore 90.906 ═══ */

/* ═══ RETIRAR Y ELIMINAR EN UN DESPLEGABLE: HOVER PLANO EN EL ROJO DE LA VALIDACIÓN (chore 90.909) ══════════════════════
   «El hover de retirar, eliminar, etc. en un dropdown debe ser plano y con el color rojo de los errores de validación».
   La fila del «···» no es un botón: sin la luz de la 90.835 (el ::after), sin canto ni sombra; al apuntarla, un velo liso
   del rojo y la letra en el rojo de los mensajes de error (--bf-danger-text, el de .bf-hint.bad). */
.bf-more .pop .bf-btn.danger::after { display: none; }

.bf-more .pop .bf-btn.danger:hover:not(:disabled),
.bf-more .pop .bf-btn.danger:focus-visible {
  background: color-mix(in srgb, var(--bf-danger) 11%, transparent);
  box-shadow: none;
  color: var(--bf-danger-text);
}

/* ═══ fin del bloque de la chore 90.909 ═══ */

/* ═══ EL ICONO DE LA CABECERA, EN LA COLUMNA DE LOS DEL CARRIL (chore 90.915) ════════════════════════════════════════════
   «El icono de los header de los modales debe estar alineado con el icono de las secciones de su panel navigator». Los
   del carril quedan centrados a 1,375 rem del canto (su relleno de 0,5 rem y la mitad de su botón de 1,75 rem); el glifo
   de la cabecera (1 rem) estaba a 1,25 rem de relleno, 6 px más a la derecha. Con carril, el relleno pone su centro ahí. */
.bf-menued-panel.has-rail > header:has(> .glyph) {
  padding-left: calc(0.5rem + 1.75rem / 2 - 0.5rem);
}

/* ═══ fin del bloque de la chore 90.915 ═══ */

/* ═══ CON SU «···» ABIERTO, LA TARJETA DE CRISTAL SE QUEDA QUIETA (chore 90.918) ═══════════════════════════════════════════
   «Al hacer hover sobre un card item cristal no debe moverse el dropdown suyo si está abierto». El desplegable va DENTRO de
   la tarjeta, así que su giro (--tilt), su subida (--lift) y el paralaje de sus piezas lo arrastraban. Mientras el «···»
   está abierto, la tarjeta no gira, no sube y sus piezas no se desplazan; al cerrarlo, vuelve el relieve. */
:is(.bf-home-card, .bf-hcard)[data-relief]:has(.bf-more[open]),
:is(.bf-home-card, .bf-hcard)[data-relief]:has(.bf-more[open]):hover {
  --tilt: none;
  transform: none;
  translate: none;
}

:is(.bf-home-card, .bf-hcard)[data-relief]:has(.bf-more[open]) > :is(.card-preview, .card-body) > * {
  translate: none;
}

/* ═══ fin del bloque de la chore 90.918 ═══ */

/* ═══ EL HOVER DEL CTA, COMO UNA BARRA LED (chore 90.919) ═════════════════════════════════════════════════════════════════
   «El hover de los CTA debe poner el borde de arriba pero sólo por en medio y tirando a transparente, y el de abajo igual,
   con un color naranja pero iluminado, como una barra LED pero sin provocar un gradiente de luz». Al apuntar, dos filetes
   de un píxel —arriba y abajo— encendidos en el centro y apagándose hacia los lados, en un naranja claro e iluminado; la
   capa de la 90.835 (::after, con su fundido) los lleva en vez de la luz radial, y se va el halo de debajo: ninguna luz se
   derrama, sólo se encienden las dos barras. */
.bf-btn.primary:not([aria-busy="true"])::after {
  background:
    linear-gradient(90deg, transparent 10%, var(--bf-cta-led) 50%, transparent 90%) top / 100% 1px no-repeat,
    linear-gradient(90deg, transparent 10%, var(--bf-cta-led) 50%, transparent 90%) bottom / 100% 1px no-repeat;
}

.bf-btn.primary.primary:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) {
  box-shadow: none;
}

/* ═══ fin del bloque de la chore 90.919 ═══ */

/* ═══ EL CTA CON MÁS CONTRASTE, DETRÁS DE TODAS SUS CAPAS (revisión de la chore 90.879) ═════════════════════════════════════
   El bloque de prefers-contrast de la 90.802 quitaba el degradado y la sombra, pero las capas que llegaron después (la
   90.811, el halo del 2026-10-06, la 90.879 y la 90.919) van más abajo en la hoja con el mismo peso o más, y al apuntar el
   botón se los devolvían. Este va detrás de la última y apaga además las dos barras de la 90.919. El aro del foco es un
   outline, así que no se toca. */
@media (prefers-contrast: more) {
  .bf-btn.primary,
  .bf-btn.primary.primary:is(:hover, :focus-visible):not(:disabled):not([aria-disabled="true"]) {
    background-image: none;
    box-shadow: none;
  }

  .bf-btn.primary:not([aria-busy="true"])::after {
    content: none;
  }
}

/* ═══ fin del bloque del CTA con más contraste ═══ */

/* ═══ CATÁLOGO DE PERMISOS: EL ROL ELEGIDO, SIN FILETE (chore 90.920) ══════════════════════════════════════════════════════
   «El seleccionado en esa vista no debe tener un borde izquierdo.» La tarjeta del rol elegido ya se distingue por su velo y
   su canto; la barra naranja de su izquierda (y la de debajo bajo el corte compacto) sobra. */
.bf-catread-role.on::before {
  content: none;
}

/* ═══ fin del bloque de la chore 90.920 ═══ */

/* ═══ EL ICONO DE LA PESTAÑA, A LA ALTURA DE SU TEXTO (chore 90.921) ═════════════════════════════════════════════════════════
   El icono se centra en la caja de la línea, pero la Segoe UI dibuja la letra por debajo de ese centro (su ascendente es
   mucho mayor que su descendente): el centro de las minúsculas y mayúsculas cae algo más de un píxel más abajo, y el icono
   parecía subido. Se baja lo que la letra pide, en em para que acompañe al tamaño de texto que elija cada cual. */
.bf-tabs :is(a, button) > svg {
  translate: 0 0.08em;
}

/* ═══ fin del bloque de la chore 90.921 ═══ */

/* ═══ LA CASILLA Y SU TEXTO, CON LA MANO (chore 90.925) ═══════════════════════════════════════════════════════════════════
   «Los textos de los checkbox deben tener cursor pointer.» Pulsar el texto de una casilla la marca (es su <label>), pero
   el cursor de texto no lo decía. Se pone la mano en la casilla habilitada y en el texto que la gobierna, sea cual sea la
   forma: la etiqueta que la envuelve, o la que va a su lado con `for`. Deshabilitada, ni casilla ni texto la llevan. */
input[type="checkbox"]:not(:disabled),
label:has(input[type="checkbox"]:not(:disabled)),
input[type="checkbox"]:not(:disabled) ~ label,
label[for]:has(+ input[type="checkbox"]:not(:disabled)) {
  cursor: pointer;
}

label:has(input[type="checkbox"]:disabled) {
  cursor: default;
}

/* ═══ fin del bloque de la chore 90.925 ═══ */

/* ═══ LA BANDA DE FILTROS, MÁS CUIDADA (chore 90.923) ═════════════════════════════════════════════════════════════════════
   «Hacer visualmente más atractivo el menú de filtros y más premium.» Tres cosas, sin cambiar el marcado (lo pinta
   <bf-toolbar> en todas las pantallas):
     · La cabecera se lee como un mando: el embudo en su baldosa (naranja flojo cuando hay filtros puestos), el rótulo con
       la tinta del texto y el galón en un círculo que gira suave.
     · Cada eje es UNA cápsula del mismo alto: el rótulo en versalitas como prefijo y el valor dentro, sin caja propia;
       el estado (las pastillas con gota) va en la misma cápsula. Puesto (distinto del de serie), la cápsula lo dice con
       su canto y su valor en naranja; al apuntarla se aviva el canto y con el foco aparece un halo fino.
     · El cuerpo entra fundiéndose y bajando un pelo, como lo que flota en la casa. */
.bf-filterbox-head {
  gap: 0.625rem;
  color: var(--bf-surface-fg);
}

.bf-filterbox-head:hover {
  background-color: color-mix(in srgb, var(--bf-muted) 5%, transparent);
}

.bf-filterbox-head > svg:first-child {
  box-sizing: content-box;
  padding: 0.3125rem;
  border-radius: 0.5rem;
  background-color: color-mix(in srgb, var(--bf-muted) 10%, transparent);
  color: var(--bf-muted);
  transition:
    background-color 320ms var(--bf-ease-out),
    color 320ms var(--bf-ease-out);
}

.bf-filterbox:has(.bf-filterbox-count:not([hidden])) > .bf-filterbox-head > svg:first-child {
  background-color: color-mix(in srgb, var(--bf-accent) 14%, transparent);
  color: var(--bf-accent-text);
}

.bf-filterbox-chev {
  display: inline-grid;
  place-items: center;
  width: 1.625rem;
  height: 1.625rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 8%, transparent);
  transition:
    transform 360ms cubic-bezier(0.2, 0.8, 0.2, 1),
    background-color 240ms var(--bf-ease-out),
    color 240ms var(--bf-ease-out);
}

.bf-filterbox-chev > svg {
  width: 13px;
  height: 13px;
}

.bf-filterbox-head:hover .bf-filterbox-chev {
  background-color: color-mix(in srgb, var(--bf-muted) 15%, transparent);
  color: var(--bf-surface-fg);
}

/* El cuerpo: las cápsulas con aire parejo, y su entrada. */
.bf-filterbox .bf-filterbox-body {
  align-items: center;
  gap: 0.625rem;
  padding-bottom: 1rem;
}

/* Desde el 2026-10-08 también en Usuarios, que ya no flota (ver el bloque de las chores 90.803–90.805). */
.bf-filterbox > .bf-filterbox-body {
  opacity: 0;
  translate: 0 -0.25rem;
  transition:
    opacity 280ms var(--bf-ease-out),
    translate 280ms var(--bf-ease-out);
}

.bf-filterbox[open] > .bf-filterbox-body {
  opacity: 1;
  translate: 0 0;
}

@starting-style {
  .bf-filterbox[open] > .bf-filterbox-body {
    opacity: 0;
    translate: 0 -0.25rem;
  }
}

/* LA CÁPSULA: un desplegable con su rótulo delante. El estado ya no (2026-10-09, «el toggle de filtros de estado debe
   tener los estilos del resto de toggles»): es el carril con gota de los demás conmutadores, con su leyenda delante. */
.bf-filterbox .bf-filterbox-body .bf-filter:has(> select) {
  --bf-capsule-edge: var(--bf-edge-line);
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  height: var(--bf-control-height);
  padding: 0.125rem 0.125rem 0.125rem 0.875rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-control-bg) 75%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-capsule-edge);
  font-size: var(--bf-text-mini);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--bf-muted);
  transition:
    box-shadow 240ms var(--bf-ease-out),
    background-color 240ms var(--bf-ease-out);
}

.bf-filterbox .bf-filterbox-body .bf-filter:has(> select):hover {
  --bf-capsule-edge: color-mix(in srgb, var(--bf-muted) 34%, transparent);
  background-color: var(--bf-control-bg);
}

.bf-filterbox .bf-filterbox-body .bf-filter:has(> select).on {
  --bf-capsule-edge: color-mix(in srgb, var(--bf-accent) 55%, transparent);
  background-color: color-mix(in srgb, var(--bf-accent) 7%, var(--bf-control-bg));
}

.bf-filterbox .bf-filterbox-body .bf-filter:has(select:focus-visible) {
  --bf-capsule-edge: var(--bf-cta-edge);
  box-shadow:
    inset 0 0 0 1px var(--bf-capsule-edge),
    0 0 0 3px color-mix(in srgb, var(--bf-accent) 16%, transparent);
}

/* El valor, dentro de la cápsula y sin caja: hereda su redondeo para que el foco y el clic cubran la parte del valor. */
.bf-filterbox .bf-filterbox-body .bf-filter > select {
  height: 100%;
  padding: 0 1.875rem 0 0.375rem;
  border: 0;
  border-radius: 999px;
  background-color: transparent;
  background-position: right 0.75rem center;
  box-shadow: none;
  font-weight: 500;
  color: var(--bf-surface-fg);
}

.bf-filterbox .bf-filterbox-body .bf-filter > select:hover,
.bf-filterbox .bf-filterbox-body .bf-filter > select:focus-visible {
  background-color: transparent;
  border: 0;
  box-shadow: none;
}

.bf-filterbox .bf-filterbox-body .bf-filter.on > select {
  color: var(--bf-accent-text);
}

/* El estado: la leyenda como prefijo, con la letra de los de las cápsulas («ROL», «IDIOMA»), y detrás el carril con
   gota tal cual, el de «Tarjetas | Tabla» (2026-10-09: antes iba dentro de una cápsula, sin el velo ni el filete del
   carril, y sus opciones medían el alto de la cápsula). */
.bf-filterbox .bf-filterbox-body .bf-fchips > legend {
  margin-right: 0.5rem;
  font-size: var(--bf-text-mini);
  font-weight: 650;
  letter-spacing: 0.06em;
  line-height: var(--bf-control-height);
  color: var(--bf-muted);
}

/* El buscador, del mismo material y del mismo alto que las cápsulas: en el panel claro, su velo blanco sobre blanco no
   se veía. Con el foco manda el de los campos (90.903). Los dos, al alto de mando del sistema (S-11; medían 31 px). */
.bf-filterbox .bf-filterbox-body .bf-field-search {
  height: var(--bf-control-height);
  padding-left: 0.875rem;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-control-bg) 75%, transparent);
}

.bf-filterbox .bf-filterbox-body .bf-field-search:not(:focus-within) {
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

.bf-filterbox .bf-filterbox-body .bf-field-search:not(:focus-within):hover {
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-muted) 34%, transparent);
}

@media (prefers-reduced-motion: reduce) {
  .bf-filterbox > .bf-filterbox-body,
  .bf-filterbox-chev {
    transition: none;
    translate: none;
  }
}

@media (forced-colors: active) {
  .bf-filterbox .bf-filterbox-body .bf-filter:has(> select) {
    border: 1px solid CanvasText;
  }
}

/* ═══ fin del bloque de la chore 90.923 ═══ */

/* ═══ AUDITORÍA: SIN ACTIVIDAD EN LA VENTANA, LA TARJETA A TODO EL ALTO (chore 90.924) ═══════════════════════════════════════
   «Cuando pone que no hay actividad en 24 h, la card debe ocupar el alto de la vista.» La tarjeta del vacío del panorama
   se mide como su rejilla (overview.ts, --fill-h) y su aviso queda en el centro de lo que ocupa. */
.bf-aud-empty[data-fill] {
  display: grid;
  min-height: var(--fill-h, auto);
}

.bf-aud-empty[data-fill] > .in {
  display: grid;
  place-items: center;
}

/* ═══ fin del bloque de la chore 90.924 ═══ */

/* ═══ EL SUBRAYADO DE LOS ENLACES SE DIBUJA DESDE LA IZQUIERDA (chore 90.927) ═════════════════════════════════════════════
   «El hover de los enlaces debe tener un underline que crezca suavemente de izquierda a derecha.» Es la receta del
   `.bf-authlink` del acceso, llevada a los enlaces de TEXTO —los de un párrafo, una pista, un aviso, una celda o la guía
   de una pantalla—: en vez del subrayado de golpe, un filete de un píxel del color del enlace que crece en el sentido en
   que se lee. Va por fondo (y no por un ::after) para que un enlace partido en dos renglones se subraye en los dos. Fuera
   quedan los enlaces que son mando (botones, pestañas, celdas, fichas, pastillas), los de una fila que se abre entera
   (`tr[data-href]`, chore 90.963: la persona de Usuarios se subrayaba al apuntar la fila) y quien pidió «Subrayar
   enlaces», que los quiere subrayados siempre. Movimiento reducido: aparece sin crecer. */
:root:not([data-links="always"]) :is(
  :is(p, dd, td, small, figcaption, .bf-hint, .bf-msg, .lead) a:not(.bf-btn, [class*="bf-tab"], .bf-back, .bf-authlink,
    .bf-cell, .bf-card, .bf-chip, .bf-pill, .bf-tag, .bf-filter, [role="button"], [role="tab"], :has(> img, > svg:only-child),
    tr[data-href] *),
  a.bf-catguide-link
) {
  text-decoration: none;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 0 100%;
  background-size: 0% 1px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  transition: background-size 420ms var(--bf-ease-out);
}

:root:not([data-links="always"]) :is(
  :is(p, dd, td, small, figcaption, .bf-hint, .bf-msg, .lead) a:not(.bf-btn, [class*="bf-tab"], .bf-back, .bf-authlink,
    .bf-cell, .bf-card, .bf-chip, .bf-pill, .bf-tag, .bf-filter, [role="button"], [role="tab"], :has(> img, > svg:only-child),
    tr[data-href] *),
  a.bf-catguide-link
):is(:hover, :focus-visible) {
  text-decoration: none;
  background-size: 100% 1px;
}

@media (prefers-reduced-motion: reduce) {
  :root:not([data-links="always"]) :is(:is(p, dd, td, small, figcaption, .bf-hint, .bf-msg, .lead) a, a.bf-catguide-link) {
    transition: none;
  }
}

:root[data-reduce~="motion"] :is(:is(p, dd, td, small, figcaption, .bf-hint, .bf-msg, .lead) a, a.bf-catguide-link) {
  transition: none;
}

/* El enlace de una fila que se abre entera ES la fila (chore 90.963): ni el filete que crece ni el subrayado de golpe
   del `a:hover` de siempre. La fila ya dice que se apunta con su realce. */
:root:not([data-links="always"]) tr[data-href] td a:is(:hover, :focus-visible) {
  text-decoration: none;
}

/* ═══ fin del bloque de la chore 90.927 ═══ */

/* ═══ LA FICHA DE PRODUCTO, CON LA COLUMNA DE IDENTIDAD DE LA DE INSTANCIA (chores 90.928 y 90.929) ═══════════════════════════
   «El rediseño más premium basándose en el actual de una instancia con la columna izquierda para la vista de un producto»
   y «el toggle de Producto debe ser igual a los de los otros módulos». La ficha usa las piezas de la de instancia
   (.bf-ific, .bf-iside, .bf-ivtabs, .bf-iside-facts) con la navegación vertical; aquí sólo lo propio del producto:
     · la baldosa lleva su color o su logo —en un producto el color es su identidad, como en su tarjeta (90.602)—;
     · su descripción, en voz baja bajo la identidad;
     · Roles y Menú: la banda de filtros y la tabla, del cristal de la casa y soldadas (el panel separa sus piezas con
       aire, y la banda y su tabla son una pieza);
     · la importación del manifiesto, plegada en su propia fila dentro de la sección. */
.bf-pside .bf-itile.tone {
  background: color-mix(in srgb, var(--hc-tone) 18%, var(--bf-card-veil));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--hc-tone) 26%, transparent);
  color: color-mix(in srgb, var(--hc-tone) 70%, var(--bf-surface-fg));
}

.bf-pside .bf-itile.logo {
  background: color-mix(in srgb, var(--bf-surface-fg) 5%, var(--bf-card-veil));
}

.bf-pside .bf-itile.logo > img {
  display: block;
  width: 2.375rem;
  height: 2.375rem;
  object-fit: contain;
  pointer-events: none;
  user-select: none;
}

.bf-iside-lead {
  display: -webkit-box;
  margin: -0.25rem 0 0;
  overflow: hidden;
  color: var(--bf-muted);
  font-size: var(--bf-text-label);
  line-height: 1.5;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 4;
}

.bf-pific .bf-ipane > .bf-card,
.bf-pific .bf-ipane > .bf-toolbar:has(> .bf-filterbox) {
  border-color: var(--bf-lg-line);
  background-color: var(--bf-lg-fill);
  -webkit-backdrop-filter: var(--bf-card-glass-filter);
  backdrop-filter: var(--bf-card-glass-filter);
}

.bf-pific .bf-ipane > .bf-toolbar:has(> .bf-filterbox) + .bf-card {
  margin-top: -1rem;
}

@media (prefers-reduced-transparency: reduce) {
  .bf-pific .bf-ipane > :is(.bf-card, .bf-toolbar:has(> .bf-filterbox)) {
    background-color: var(--bf-card-bg);
  }
}

:root[data-reduce~="transparency"] .bf-pific .bf-ipane > :is(.bf-card, .bf-toolbar:has(> .bf-filterbox)) {
  background-color: var(--bf-card-bg);
}

/* La importación del manifiesto: una fila que se despliega, con el galón en su círculo como la banda de filtros. */
.bf-pimport {
  border-top: 1px solid var(--bf-edge-line);
  interpolate-size: allow-keywords;
}

.bf-pimport::details-content {
  block-size: 0;
  overflow-y: clip;
  transition:
    block-size var(--bf-dur-base) var(--bf-ease-out),
    content-visibility var(--bf-dur-base) var(--bf-ease-out);
  transition-behavior: allow-discrete;
}

.bf-pimport[open]::details-content {
  block-size: auto;
}

.bf-pimport > summary {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.75rem 1.25rem;
  color: var(--bf-surface-fg);
  font-size: var(--bf-text-ui);
  font-weight: 600;
  list-style: none;
  cursor: pointer;
  transition: background-color 240ms var(--bf-ease-out);
}

.bf-pimport > summary::-webkit-details-marker {
  display: none;
}

.bf-pimport > summary:hover {
  background-color: color-mix(in srgb, var(--bf-muted) 5%, transparent);
}

.bf-pimport > summary:focus-visible {
  outline: 2px solid var(--bf-accent);
  outline-offset: -2px;
}

.bf-pimport > summary > svg:first-child {
  width: 15px;
  height: 15px;
  flex: none;
  color: var(--bf-muted);
}

.bf-pimport-chev {
  display: inline-grid;
  place-items: center;
  width: 1.625rem;
  height: 1.625rem;
  margin-left: auto;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-muted) 8%, transparent);
  color: var(--bf-muted);
  transition: transform 360ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.bf-pimport-chev > svg {
  width: 13px;
  height: 13px;
  transform: rotate(90deg); /* el galón del sprite apunta a la derecha; aquí, hacia abajo */
}

.bf-pimport[open] .bf-pimport-chev {
  transform: rotate(180deg);
}

@media (prefers-reduced-motion: reduce) {
  .bf-pimport::details-content,
  .bf-pimport-chev {
    transition: none;
  }
}

/* ═══ fin del bloque de las chores 90.928 y 90.929 ═══ */

/* ═══ «DAR ACCESO»: LOS SELECTORES SIN BORDE (chore 90.930) ════════════════════════════════════════════════════════════════
   «Rediseñar dar acceso de usuarios como modal y sin bordes en los selectores.» En su formulario —el modal y la página de
   alta, que es el mismo— los desplegables y el buscador de usuarios pierden el filete que los campos llevan en claro
   dentro de un modal o una tarjeta (90.838): se distinguen por un relleno de la tinta del texto, apenas, que se aviva al
   apuntarlos. El foco sigue con su anillo fino (90.903). */
form[data-slice="userInstanceRoles-grant"] :is(select, input.bf-input) {
  --bf-field-edge: transparent;
  --bf-field-bg: color-mix(in srgb, var(--bf-surface-fg) 5%, transparent);
  border-color: transparent;
}

form[data-slice="userInstanceRoles-grant"] :is(select, input.bf-input):hover:not(:focus-visible) {
  --bf-field-bg: color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  background-color: var(--bf-field-bg);
}

/* La lista de usuarios del buscador es otro selector: del mismo relleno que los campos, sin caja blanca ni filete, y su pie
   en la misma pieza. */
form[data-slice="userInstanceRoles-grant"] .bf-pick .pop {
  border-color: transparent;
  background-color: color-mix(in srgb, var(--bf-surface-fg) 4%, transparent);
  /* El canto en degradado de lo flotante (se pinta en el área del borde) también se va: es el marco que se veía. */
  background-image: none;
  box-shadow: none;
}

/* El usuario elegido, sin la barra de su izquierda (como el rol elegido del catálogo, 90.920): lo dice su velo. */
form[data-slice="userInstanceRoles-grant"] .bf-pick :is(.opt.on, .opt:has(input:checked)) {
  box-shadow: none;
}

form[data-slice="userInstanceRoles-grant"] .bf-pick .pfoot {
  border-top-color: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  background-color: transparent;
}

/* ═══ fin del bloque de la chore 90.930 ═══ */

/* ═══ «NUEVO ROL» EN UN MODAL: EL MODO DE PARTIDA (chore 90.931) ═══════════════════════════════════════════════════════════
   En el modal los dos modos («Vacío» · «Copiar de un rol») son dos radios con la forma del estado en pastillas; el
   desplegable del rol de origen sólo se ve al elegir «Copiar», y la pista de «Vacío» sólo con «Vacío». */
.bf-rstart {
  display: grid;
  gap: 0.75rem;
  justify-items: start;
}

.bf-rstart > .bf-field {
  justify-self: stretch;
}

.bf-rstart .bf-fchips label > svg {
  width: 14px;
  height: 14px;
  flex: none;
}

/* Cada modo de partida enseña sólo lo suyo: el rol de origen, la plantilla (00.002 1.42.0) o el aviso del vacío. */
.bf-rstart:has(input[name="start"]:not([value="copy"]):checked) .bf-rstart-copy,
.bf-rstart:has(input[name="start"]:not([value="template"]):checked) .bf-rstart-template,
.bf-rstart:has(input[name="start"]:not([value="empty"]):checked) .bf-rstart-empty {
  display: none;
}

/* ═══ fin del bloque de la chore 90.931 ═══ */

/* ═══ MODALES DE FORMULARIO: EL CLIC FUERA Y EL BORRADOR (chore 90.932) ═══════════════════════════════════════════════════
   Con cambios, un clic fuera del modal no lo cierra: el cuadro da un empujoncito —crece un punto y se asienta— para decir
   que el clic ha llegado (ts/ui/form-draft.ts, `nudge`). Y si al abrir se repone un borrador, su aviso va arriba de la
   columna, con «Empezar de cero» a su derecha. */
@keyframes bf-nudge {
  0%,
  100% {
    transform: none;
  }

  35% {
    transform: scale(1.012);
  }

  70% {
    transform: scale(0.997);
  }
}

.bf-modal.bf-nudge {
  animation: bf-nudge 340ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

@media (prefers-reduced-motion: reduce) {
  .bf-modal.bf-nudge {
    animation: none;
    box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-accent) 45%, transparent);
  }
}

:root[data-reduce~="motion"] .bf-modal.bf-nudge {
  animation: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--bf-accent) 45%, transparent);
}

.bf-draftnote {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem 1rem;
}

.bf-draftnote > .bf-btn {
  flex: none;
}

/* ═══ fin del bloque de la chore 90.932 ═══ */

/* ═══ EL ACCESO EN EL TELÉFONO: EL INMERSIVO SE VE DETRÁS DE «INICIAR SESIÓN» (chore 90.934) ═══════════════════════════════
   «En el login de móvil, por detrás de iniciar sesión debe salir el inmersivo 3D en movimiento, desenfocado.» En el
   teléfono la tarjeta ocupa casi toda la pantalla y su velo (40 % en claro, 25 % en oscuro) con 12 px de desenfoque se comía
   la escena: apenas unas manchas. Mobile-first (MF-2): en el teléfono el velo baja a algo más de la mitad, el desenfoque a
   6 px —la planta queda en luces que se mueven, sin aristas que compitan con el texto— y la escena a su tinta plena; desde
   900 px, el acceso de escritorio tal como estaba. */
.bf-door-sky {
  --bf-door-orbit-dim: 1.25;
}

.bf-door-sky:has(> .bf-door-plate) ~ .bf-auth .form {
  --bf-door-panel-filter: blur(6px) saturate(1.5);
  background-color: color-mix(in srgb, var(--bf-door-glass-tint) calc(var(--bf-door-panel-veil) * 0.55), transparent);
}

@media (min-width: 900px) {
  .bf-door-sky {
    --bf-door-orbit-dim: 1;
  }

  .bf-door-sky:has(> .bf-door-plate) ~ .bf-auth .form {
    --bf-door-panel-filter: blur(12px) saturate(1.35);
    background-color: color-mix(in srgb, var(--bf-door-glass-tint) var(--bf-door-panel-veil), transparent);
  }
}

/* ═══ fin del bloque de la chore 90.934 ═══ */

/* ═══ LOS BOTONES NO SE SELECCIONAN Y EL CTA OCUPADO CONSERVA SU TINTA (chore 90.942) ══════════════════════════════════════
   «Al pulsar dos veces en un botón no debe seleccionar el texto, y a veces al hacer clic los CTA se pone su texto en
   blanco.» Dos causas:
     · El doble clic seleccionaba la palabra del rótulo y el resaltado del sistema la pintaba en blanco. Lo que se pulsa
       —botones, pestañas, conmutadores y las pastillas del estado— no es texto que copiar: no se selecciona.
     · Al enviar, el botón queda `aria-busy` y deshabilitado, y esa regla (S-9) le ponía la tinta de los secundarios
       (--bf-button-fg, blanca en oscuro) también al primario: su rótulo se volvía blanco sobre el naranja mientras
       esperaba. El primario ocupado conserva la suya. */
:is(.bf-btn, button, [role="button"], input:is([type="submit"], [type="button"], [type="reset"]),
  .bf-tabs > :is(a, button), .bf-seg > *, .bf-fchips label) {
  -webkit-user-select: none;
  user-select: none;
}

.bf-btn.primary[aria-busy="true"]:disabled,
.bf-btn.primary[aria-busy="true"] {
  color: var(--bf-on-cta);
}

/* ═══ fin del bloque de la chore 90.942 ═══ */

/* ═══ LA GOTA DE LA OPCIÓN PUESTA, MÁS DISIMULADA (chore 90.943) ══════════════════════════════════════════════════════════
   «El seleccionado de un toggle con efecto gota debe ser más disimulado» (y, a la segunda, «hazlo mejor»). La gota pierde
   peso sin perder su forma: un velo tenue (claro 26 → 22 %, oscuro 4,5 → 3,5 %), un canto de un pelo, apenas realce de lo
   de detrás y NINGUNA sombra fuera —ni la caída ni el halo naranja—: sólo un hilo de luz arriba. Va también en la variante
   que se desliza (`[data-glide]`), que es la de casi todos los conmutadores y tenía su propia sombra, más fuerte; por eso
   en claro la primera pasada apenas se notaba. La opción puesta la dice su canto y su tinta, no un relleno. */
/* Los valores, por tema, en tokens.css. El segundo selector no filtra nada: da a la regla el peso de las reglas oscuras
   de las gotas anteriores (:root[data-theme="dark"] … y :root:not([data-theme="light"]) …), para que por orden gane
   esta también de noche. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row),
:root:not([data-theme="light"]) :is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) {
  --gota-top: var(--bf-seg-gota-top);
  --gota-bottom: var(--bf-seg-gota-bottom);
  --gota-hi: var(--bf-seg-gota-hi);
  --gota-lo: transparent;
  --gota-drop: transparent;
  --gota-spec-soft: var(--bf-seg-gota-spec-soft);
  --gota-lift: var(--bf-seg-gota-lift);
  --gota-line: var(--bf-seg-gota-line);
}

:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink,
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row)[data-glide] > .bf-tabs-ink {
  box-shadow: inset 0 1px 0 var(--gota-hi);
}

/* El canto, un filete uniforme y fino: sin el degradado del cristal (--bf-glass-rim), que en claro arranca en la tinta
   cálida al 20 % y era lo que más pesaba de la gota. */
:is(.bf-page-header .acts > .bf-tabs, .bf-tabs[role="group"], .bf-usections, .bf-seg, .bf-fchips > .row) > .bf-tabs-ink::before {
  background: linear-gradient(var(--gota-line), var(--gota-line));
}

/* ═══ fin del bloque de la chore 90.943 ═══ */

/* ═══ MÓVIL P2 · LA FICHA COMPACTA (chore 90.935, plan de móvil de la 90.864) ══════════════════════════════════════════════
   Bajo el corte del lateral la columna de identidad se apilaba arriba con TODO: la baldosa grande, las acciones partidas
   en dos o tres renglones, la navegación y los hechos; la pestaña empezaba en la segunda pantalla. Ahora es una cabecera
   compacta: baldosa menor y sin el rótulo «Estado», las acciones del registro en UN renglón que rueda de lado, la
   navegación a sangre en su carril, la descripción en dos renglones y, a ancho de teléfono, sin los hechos (los mismos
   datos están en la pestaña Datos). Sirve a las cuatro fichas que comparten la columna: instancia, usuario, producto y
   proveedor.
   MF-3, excepción anotada: va con `max-width` porque continúa el corte de la columna de identidad (bloque 90.787), que
   ya baja con él; reescribirlo en mobile-first sería tocar la ficha de escritorio entera. */
@media (max-width: 899.98px) {
  .bf-iside {
    gap: 0.75rem;
    padding: 0.875rem;
  }

  .bf-iside .bf-itile {
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 0.8rem;
  }

  .bf-iside .bf-itile > svg {
    width: 1.25rem;
    height: 1.25rem;
  }

  .bf-iside-id .who .lbl {
    display: none;
  }

  .bf-iside-lead {
    -webkit-line-clamp: 2;
  }

  .bf-iside-acts,
  .bf-ific .bf-ivtabs {
    flex-wrap: nowrap;
    margin-inline: -0.875rem;
    padding-inline: 0.875rem;
    overflow-x: auto;
    scroll-padding-inline: 0.875rem;
    scrollbar-width: none;
  }

  .bf-iside-acts::-webkit-scrollbar,
  .bf-ific .bf-ivtabs::-webkit-scrollbar {
    display: none;
  }

  .bf-iside-acts .bf-btn,
  .bf-iside-acts .bf-btn.open {
    flex: none;
  }

  .bf-ific .bf-ivtabs {
    padding-top: 0.5rem;
  }
}

@media (max-width: 619.98px) {
  .bf-iside-facts {
    display: none;
  }
}

/* ═══ fin del bloque de la chore 90.935 ═══ */

/* ═══ MÓVIL P1 · EL ÁRBOL DE CARPETAS, PLEGADO (chore 90.936, plan de móvil de la 90.864) ═════════════════════════════════
   Por debajo de las dos columnas el árbol de Instancias y Productos va delante de la lista y se comía el primer
   pantallazo. Plegado, queda su cabecera con un botón que dice en qué carpeta se está («Todas ▾»); al pulsarlo el árbol se
   abre en el sitio, y elegir una carpeta navega como siempre. Sólo con el módulo cargado (`data-fold`): sin JavaScript el
   árbol va entero. Desde las dos columnas, el botón no está y el árbol es la columna de siempre.
   El corte es el ancho (900, MF-3), el mismo en que la rejilla de carpetas pasa a dos columnas (bloque 90.677); se
   escribe como su contrario para no tocar la columna de escritorio. Era 64 rem hasta el 2026-10-13. */
.bf-ftree-toggle {
  display: none;
}

@media (max-width: 899.98px) {
  .bf-folders-side[data-fold]:not(.is-open) > :is(.bf-ftree, .bf-ftree-find) {
    display: none;
  }

  .bf-folders-side[data-fold] .bf-ftree-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    min-width: 0;
    max-width: 60%;
    height: calc(var(--bf-control-height) + 0.125rem);
    margin-left: auto;
    padding: 0 0.625rem 0 0.75rem;
    border: 0;
    border-radius: 999px;
    background-color: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
    box-shadow: inset 0 0 0 1px var(--bf-edge-line);
    color: var(--bf-surface-fg);
    font: inherit;
    font-size: var(--bf-text-ui);
    font-weight: 600;
    cursor: pointer;
  }

  .bf-ftree-toggle > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .bf-ftree-toggle > svg {
    flex: none;
    width: 14px;
    height: 14px;
    color: var(--bf-muted);
    transform: rotate(90deg); /* el galón del sprite apunta a la derecha; plegado, hacia abajo */
    transition: transform 280ms cubic-bezier(0.2, 0.8, 0.2, 1);
  }

  .bf-folders-side.is-open .bf-ftree-toggle > svg {
    transform: rotate(-90deg);
  }

  /* El «+» de carpeta nueva se queda a la derecha del botón. */
  .bf-folders-side[data-fold] .bf-ftree-head > .bf-iconbtn {
    margin-left: 0.25rem;
  }

  .bf-folders {
    gap: 0.75rem;
  }
}

/* ═══ fin del bloque de la chore 90.936 ═══ */

/* ═══ MÓVIL P3 · LA BANDA DE FILTROS EN EL TELÉFONO (chore 90.937, plan de móvil de la 90.864) ═════════════════════════════
   Llega cerrada (ui/filters.ts) y enseña lo puesto en chips bajo «Filtros»; abierta, es una HOJA que sube del borde de
   abajo, con el material de los modales: su cristal, su caída y el velo desenfocado detrás. La hoja es el cuerpo de la
   banda hecho `popover` por el módulo, así que va en la capa superior, por encima de la barra inferior del shell. Sin el
   módulo (o sin `popover`) la banda se abre en el sitio y sus cápsulas van en un renglón que rueda de lado, con el
   buscador delante. MF-3, excepción anotada: sólo existe en el teléfono. */
.bf-filterbox-sheethead,
.bf-filterbox-chips {
  display: none;
}

@media (max-width: 619.98px) {
  .bf-filterbox .bf-filterbox-body:not([popover]) {
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: none;
    scroll-padding-inline: 0.75rem;
  }

  .bf-filterbox .bf-filterbox-body:not([popover])::-webkit-scrollbar {
    display: none;
  }

  .bf-filterbox .bf-filterbox-body:not([popover]) > * {
    flex: none;
  }

  .bf-filterbox .bf-filterbox-body:not([popover]) > .bf-field-search {
    flex: 0 0 82%;
    width: auto;
  }

  /* La hoja. */
  .bf-filterbox .bf-filterbox-body[popover] {
    position: fixed;
    inset: auto 0 0;
    width: 100%;
    max-width: none;
    max-height: 85vh;
    max-height: 85dvh;
    margin: 0;
    padding: 0 1rem calc(1.25rem + env(safe-area-inset-bottom, 0px));
    flex-direction: column;
    flex-wrap: nowrap;
    align-items: stretch;
    gap: 0.75rem;
    overflow-y: auto;
    overscroll-behavior: contain;
    border: 0;
    border-radius: var(--bf-glass-radius) var(--bf-glass-radius) 0 0;
    background: var(--bf-modal-bg);
    -webkit-backdrop-filter: var(--bf-modal-filter);
    backdrop-filter: var(--bf-modal-filter);
    box-shadow: var(--bf-lg-edge), var(--bf-modal-soft-drop);
    color: var(--bf-surface-fg);
    transition: translate 280ms var(--bf-ease-out), display 280ms allow-discrete, overlay 280ms allow-discrete;
  }

  .bf-filterbox .bf-filterbox-body[popover]:not(:popover-open) {
    display: none;
  }

  .bf-filterbox .bf-filterbox-body[popover]:popover-open {
    display: flex;
    translate: 0 0;
  }

  @starting-style {
    .bf-filterbox .bf-filterbox-body[popover]:popover-open {
      translate: 0 100%;
    }
  }

  .bf-filterbox .bf-filterbox-body[popover]::backdrop {
    background: transparent;
    -webkit-backdrop-filter: blur(4px);
    backdrop-filter: blur(4px);
  }

  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .bf-filterbox .bf-filterbox-body[popover]::backdrop {
      background: var(--bf-scrim);
    }
  }

  /* Cada eje, a todo el ancho de la hoja. */
  .bf-filterbox .bf-filterbox-body[popover] > :not(.bf-filterbox-sheethead) {
    width: 100%;
    max-width: none;
  }

  /* Y su desplegable ocupa lo que queda de la pastilla, con el galón al final. */
  .bf-filterbox .bf-filterbox-body[popover] > .bf-filter > select {
    flex: 1;
    min-width: 0;
    max-width: none;
  }

  .bf-filterbox .bf-filterbox-body[popover] > .bf-filterbox-sheethead {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin: 0 -1rem;
    padding: 0.75rem 0.5rem 0.5rem 1rem;
    background: inherit;
    font-weight: 650;
  }

  /* Las chips de lo puesto, bajo «Filtros» con la banda cerrada. */
  .bf-filterbox:not([open]) + .bf-filterbox-chips:not([hidden]) {
    display: flex;
    flex-wrap: wrap;
    gap: 0.4rem;
    padding: 0 0.75rem 0.6rem;
  }

  .bf-filterbox-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    min-width: 0;
    max-width: 100%;
    height: 1.6rem;
    padding: 0 0.4rem 0 0.65rem;
    border: 0;
    border-radius: 999px;
    background: color-mix(in srgb, var(--bf-accent) 9%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 24%, transparent);
    color: var(--bf-surface-fg);
    font: inherit;
    font-size: var(--bf-text-label);
    cursor: pointer;
  }

  .bf-filterbox-chip > span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .bf-filterbox-chip > svg {
    flex: none;
    width: 12px;
    height: 12px;
    color: var(--bf-muted);
  }

  .bf-filterbox-chip:hover > svg {
    color: var(--bf-surface-fg);
  }
}

@media (max-width: 619.98px) and (prefers-reduced-motion: reduce) {
  .bf-filterbox .bf-filterbox-body[popover] {
    transition: none;
  }
}

:root[data-reduce~="motion"] .bf-filterbox .bf-filterbox-body[popover] {
  transition: none;
}

/* ═══ fin del bloque de la chore 90.937 ═══ */

/* ═══ MÓVIL P4 · LA LLAMADA DE LOS FORMULARIOS, A MANO (chore 90.938) ═══════════════════════════════════════════════════
   En el teléfono, «Guardar cambios», «Crear…» o «Publicar» bajaban a su propia fila bajo el título y, al rodar un
   formulario largo, quedaban lejos. En las pantallas cuya cabecera envía un formulario, sus acciones van en una barra de
   cristal fija justo encima de la barra inferior del shell, con la llamada a lo ancho; la página deja ese hueco al pie.
   Los listados no cambian: su llamada abre algo, no envía nada. MF-3, excepción anotada: sólo existe en el teléfono, que es
   donde está la barra inferior. */
@media (max-width: 619.98px) {
  .bf-page-header > .acts:has(> button.bf-btn.primary[form], > button.bf-btn.primary[type="submit"]) {
    position: fixed;
    left: 0.6rem;
    right: 0.6rem;
    bottom: calc(4.6rem + env(safe-area-inset-bottom, 0px));
    z-index: 30;
    display: flex;
    gap: 0.5rem;
    padding: 0.4rem;
    border-radius: 1.1rem;
    background: color-mix(in srgb, var(--bf-card-bg) 72%, transparent);
    -webkit-backdrop-filter: blur(18px) saturate(1.4);
    backdrop-filter: blur(18px) saturate(1.4);
    box-shadow: inset 0 0 0 1px var(--bf-edge-line), var(--bf-sticky-bar-drop);
  }

  .bf-page-header > .acts:has(> button.bf-btn.primary[form], > button.bf-btn.primary[type="submit"]) > .bf-btn.primary {
    flex: 1;
    justify-content: center;
  }

  .bf-admin-main:has(.bf-page-header > .acts > button.bf-btn.primary[form], .bf-page-header > .acts > button.bf-btn.primary[type="submit"]) {
    padding-bottom: 5rem;
  }

  /* La cabecera es `sticky` y eso ya le da su propio contexto de apilado: la barra fija se pinta DENTRO de él, a su
     altura, y las tarjetas que vienen detrás la tapaban. Sube por encima del contenido (por debajo de la barra del shell,
     40, y de los modales). */
  .bf-page-header:has(> .acts > button.bf-btn.primary[form], > .acts > button.bf-btn.primary[type="submit"]) {
    z-index: 30;
  }
}

/* ═══ fin del bloque de la chore 90.938 ═══ */

/* ═══ MÓVIL P5 · LAS TABLAS COMO TARJETAS (chore 90.939) ═══════════════════════════════════════════════════════════════════
   A 390 px las tablas enseñaban dos columnas y media (el usuario de Sesiones quedaba en «Administrado…») y la acción de
   cada fila no se veía. Bajo 620 px cada fila es una ficha: el primer dato a lo ancho y en grande, los demás en voz baja
   detrás, y las acciones a la derecha. La matriz de Asignaciones no cambia: su lectura ES la rejilla. MF-3, excepción
   anotada: la tabla en ficha sólo existe en el teléfono; desde 620 es la tabla de siempre. */
@media (max-width: 619.98px) {
  table.bf-table:not(.bf-matrix),
  table.bf-table:not(.bf-matrix) > tbody {
    display: block;
    width: 100%;
  }

  table.bf-table:not(.bf-matrix) > :is(colgroup, thead) {
    display: none;
  }

  table.bf-table:not(.bf-matrix) > tbody > tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.25rem 0.75rem;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid var(--bf-edge-line);
  }

  table.bf-table:not(.bf-matrix) > tbody > tr > :is(td, th) {
    display: block;
    min-width: 0;
    max-width: 100%;
    padding: 0;
    border: 0;
    color: var(--bf-muted);
    font-size: var(--bf-text-label);
    text-align: start;
  }

  table.bf-table:not(.bf-matrix) > tbody > tr > :is(td, th):first-child:not(.ck),
  table.bf-table:not(.bf-matrix) > tbody > tr > .ck + :is(td, th) {
    flex: 1 1 calc(100% - 2.5rem);
    color: var(--bf-surface-fg);
    font-size: var(--bf-text-body);
  }

  table.bf-table:not(.bf-matrix) > tbody > tr > .ck {
    flex: none;
  }

  table.bf-table:not(.bf-matrix) > tbody > tr > td.acts {
    margin-left: auto;
  }

  table.bf-table:not(.bf-matrix) > tbody > tr > td[colspan] {
    flex-basis: 100%;
  }
}

/* ═══ fin del bloque de la chore 90.939 ═══ */

/* ═══ MÓVIL P6 · EN HORIZONTAL, LA ALTURA PARA EL CONTENIDO (chore 90.940) ═════════════════════════════════════════════════
   A 844 × 390 las migas, el título, el párrafo de ayuda y la acción gastaban la mitad de la altura. Con la pantalla
   tumbada y baja: el párrafo de ayuda de la cabecera se retira (lo dice la pantalla), la cabecera se aprieta y los modales
   ocupan la pantalla entera. Las fichas ya van compactas (P2, bajo 900). MF-3, excepción anotada: es una consulta de
   ALTURA, no un corte de ancho. */
@media (orientation: landscape) and (max-height: 499.98px) {
  .bf-page-header :is(.lead, .bf-usections-lead) {
    display: none;
  }

  .bf-page-header {
    gap: 0.5rem 1rem;
  }

  .bf-page-header h1 {
    font-size: 1.15rem;
  }

  .bf-overlay .bf-modal {
    height: 100dvh !important;
    max-height: 100dvh;
    border-radius: 0;
  }
}

/* ═══ fin del bloque de la chore 90.940 ═══ */

/* ═══ MÓVIL P3 · LA BANDA DE LA AUDITORÍA, PLEGADA (chore 90.937) ═══════════════════════════════════════════════════════════
   Sus cinco desplegables iban en cuatro renglones y el conmutador Cronológica · Por sesión se salía por la derecha. En el
   teléfono, con el módulo cargado (`data-fold`), quedan el buscador y un botón «Filtros» que abre los ejes en el sitio; lo
   puesto sigue a la vista en sus chips de debajo. El conmutador de vista va en su propio renglón, entero. Los
   desplegables cuelgan de su pastilla (absolutos): por eso aquí no vale el carril que rueda de la banda general.
   MF-3, excepción anotada: sólo en el teléfono. */
.bf-aud-fold {
  display: none;
}

@media (max-width: 619.98px) {
  .bf-aud-band[data-fold] {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: 0.5rem;
  }

  .bf-aud-band[data-fold] > .bf-aud-search {
    min-width: 0;
  }

  .bf-aud-band[data-fold] > .bf-aud-fold {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    height: 100%;
    padding: 0 0.75rem;
    border: 0;
    border-radius: 999px;
    background-color: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
    box-shadow: inset 0 0 0 1px var(--bf-edge-line);
    color: var(--bf-surface-fg);
    font: inherit;
    font-size: var(--bf-text-ui);
    font-weight: 600;
    cursor: pointer;
  }

  .bf-aud-band[data-fold].is-open > .bf-aud-fold {
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 45%, transparent);
    color: var(--bf-accent-text);
  }

  .bf-aud-band[data-fold] > .bf-aud-fold > svg {
    width: 15px;
    height: 15px;
  }

  .bf-aud-band[data-fold]:not(.is-open) > .bf-aud-pick {
    display: none;
  }

  .bf-aud-band[data-fold] > .bf-aud-pick {
    grid-column: 1 / -1;
    justify-self: start;
  }

  .bf-aud-band[data-fold] > .bf-aud-end {
    grid-column: 1 / -1;
    flex-wrap: wrap;
    justify-content: space-between;
    margin-left: 0;
  }
}

/* ═══ fin del bloque de la chore 90.937 (Auditoría) ═══ */

/* ═══ EL ACCESO EN PANTALLA GRANDE: EL INMERSIVO SE VE TRAS LA TARJETA (chore 90.944) ═════════════════════════════════════
   «En pantallas grandes y en cualquiera, parte del inmersivo 3D debe pasar por detrás de la card.» El encuadre ya mete el
   anillo y la planta bajo la tarjeta hasta el 40 % de su ancho (door-sky.ts); aquí el cristal deja verlos: de 12 a 8 px
   de desenfoque y el velo a un 70 % —sigue siendo esmerilado y el texto no compite—. En el teléfono manda lo de la
   90.934 (6 px y algo más de la mitad del velo). */
@media (min-width: 900px) {
  .bf-door-sky:has(> .bf-door-plate) ~ .bf-auth .form {
    --bf-door-panel-filter: blur(8px) saturate(1.45);
    background-color: color-mix(in srgb, var(--bf-door-glass-tint) calc(var(--bf-door-panel-veil) * 0.7), transparent);
  }
}

/* ═══ fin del bloque de la chore 90.944 ═══ */

/* ═══ LA BARRA DE LAS VISTAS, COMO LA DE LOS MODALES (chore 90.947) ═══════════════════════════════════════════════════════
   «El scroll de las vistas debe ser como el resto de scrolls, como el de los modales, de fino y de estilo.» El rodillo del
   documento deja la barra plana de 12 px (la «de Spotify», 2026-10-06) y vuelve a la píldora de la casa que llevan los
   modales, las tablas y las listas: fina, redondeada, en la tinta apagada y más viva al apuntarla. */
body::-webkit-scrollbar {
  width: 0.875rem;
  height: 0.875rem;
}

body::-webkit-scrollbar-thumb {
  background-color: color-mix(in srgb, var(--bf-muted) 30%, transparent);
  background-clip: padding-box;
  border: 4px solid transparent;
  border-radius: 999px;
}

body::-webkit-scrollbar-thumb:hover,
body::-webkit-scrollbar-thumb:active {
  background-color: color-mix(in srgb, var(--bf-muted) 58%, transparent);
}

/* ═══ fin del bloque de la chore 90.947 ═══ */

/* ═══ EL SELECTOR DE ARCHIVOS (chore 90.968) ════════════════════════════════════════════════════════════════════════════
   «El seleccionador de archivos debe tener un diseño más premium, elegante y elaborado». Era el control del navegador
   con la ropa de un campo. Ahora es un POZO para soltar el fichero, de la familia del pozo de pegado (.bf-paste): el
   velo del campo, el filete DISCONTINUO que invita a soltar algo y, encima, una luz muy tenue del acento que se enciende
   al apuntar. Dentro, la baldosa con la flecha que sube, la invitación escrita y, en pastillas, los tipos y el tope.
   Con el fichero elegido el pozo pasa a ser su FICHA —la hoja con la esquina doblada y su extensión, el nombre, el
   peso con su LED, «Cambiar» y el aspa—, que entra con el asiento de la casa y deja correr una vez la luz del acento
   por su canto de abajo. Lo pinta AdminFileTagHelper.cs y lo mueve ts/ui/file-pick.ts.

   El campo de verdad va ENCIMA de todo, invisible: pulsar en cualquier sitio abre el diálogo del sistema y soltar lo
   carga, también sin JavaScript. Sólo el aspa queda por encima de él. Estados, como el pozo de pegado: lleno, el
   filete continuo; con el foco, el canto naranja fino de todo campo (90.959); `over`, con un fichero encima, el acento;
   `bad`, el aviso de tipo o peso con su LED (M-2). */
.bf-filepick {
  position: relative;
  isolation: isolate;
  display: grid;
  align-content: center;
  min-height: 9.75rem;
  border: 1.5px dashed var(--bf-field-line);
  border-radius: var(--bf-radius-lg);
  background-color: var(--bf-field-bg);
  overflow: hidden;
  color: var(--bf-surface-fg);
  transition:
    border-color var(--bf-dur-base) var(--bf-ease-out),
    background-color var(--bf-dur-base) var(--bf-ease-out);
}

/* La luz del acento, de arriba: apenas en reposo, más al apuntar y del todo con un fichero encima. */
.bf-filepick::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background: radial-gradient(95% 125% at 50% -12%, color-mix(in srgb, var(--bf-accent) 13%, transparent), transparent 68%);
  opacity: 0.45;
  transition: opacity var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filepick:hover::before,
.bf-filepick.over::before {
  opacity: 1;
}

/* La luz que corre una vez por el canto de abajo cuando llega un fichero. */
.bf-filepick::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 2px;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--bf-accent) 50%, transparent);
  transform: scaleX(0);
  opacity: 0;
}

.bf-filepick.landed::after {
  animation: bf-filepick-sweep calc(var(--bf-dur-entry) * 1.6) var(--bf-ease-out) both;
}

@keyframes bf-filepick-sweep {
  0% { transform: scaleX(0); opacity: 1; }
  70% { opacity: 1; }
  100% { transform: scaleX(1); opacity: 0; }
}

/* El campo de verdad: encima de todo, del tamaño del pozo e invisible. Anula el vestido de campo de `.bf-field`. */
.bf-filepick > input[type="file"] {
  position: absolute;
  inset: 0;
  z-index: 2;
  width: 100%;
  max-width: none;
  height: 100%;
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: inherit;
  background: transparent;
  box-shadow: none;
  opacity: 0;
  cursor: pointer;
}

.bf-filepick > input[type="file"]::file-selector-button {
  cursor: pointer;
}

/* ── La invitación ─────────────────────────────────────────────────────────────────────────────────────────────────── */
.bf-filepick-call {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 1.5rem 1.25rem 1.375rem;
  text-align: center;
  pointer-events: none;
}

.bf-filepick.filled > .bf-filepick-call {
  display: none;
}

.bf-filepick-call .tile {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  margin-bottom: 0.25rem;
  border-radius: 0.95rem;
  color: var(--bf-accent);
  background: linear-gradient(160deg, color-mix(in srgb, var(--bf-accent) 18%, var(--bf-accent-soft)), var(--bf-accent-soft));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-accent) 26%, transparent);
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filepick-call .tile svg {
  width: 1.375rem;
  height: 1.375rem;
}

/* La flecha del glifo Drop del sprite es su primer trazo. */
.bf-filepick-call .tile svg > path:first-child {
  transition: transform var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filepick:hover .bf-filepick-call .tile {
  transform: translateY(-2px);
}

.bf-filepick:hover .bf-filepick-call .tile svg > path:first-child {
  transform: translateY(-1.5px);
}

.bf-filepick-call .say {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.bf-filepick-call .say b {
  font-size: var(--bf-text-body);
  font-weight: 600;
  letter-spacing: -0.005em;
}

.bf-filepick-call .say .tap,
.bf-filepick-call .say .release {
  display: none;
}

.bf-filepick-call .or {
  font-size: var(--bf-text-label);
  color: var(--bf-muted);
}

.bf-filepick-call .or u {
  color: var(--bf-accent-text);
  text-decoration-color: color-mix(in srgb, var(--bf-accent-text) 45%, transparent);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

/* Los tipos y el tope, en pastillas quietas: dicen lo que vale antes de elegir (F-8). */
.bf-filepick-call .kinds {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.375rem;
  margin-top: 0.375rem;
}

.bf-filepick-call .kinds i {
  padding: 0.3rem 0.6rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--bf-surface-fg) 8%, transparent);
  color: var(--bf-muted);
  font-size: var(--bf-text-micro);
  font-style: normal;
  font-weight: 700;
  letter-spacing: 0.08em;
  line-height: 1;
}

.bf-filepick-call .kinds i.max {
  font-weight: 600;
  letter-spacing: 0.02em;
}

/* En un teléfono no se arrastra: se toca. */
@media (pointer: coarse) {
  .bf-filepick-call .say .drop,
  .bf-filepick-call .or {
    display: none;
  }

  .bf-filepick-call .say .tap {
    display: inline;
  }
}

/* Con un fichero ENCIMA: el acento en el canto, la baldosa que se adelanta y «Suéltalo para añadirlo». */
.bf-filepick.over {
  border-style: solid;
  border-color: var(--bf-accent);
  background-color: color-mix(in srgb, var(--bf-accent) 5%, var(--bf-field-bg));
}

.bf-filepick.over .bf-filepick-call .tile {
  transform: translateY(-3px) scale(1.06);
}

.bf-filepick.over .bf-filepick-call .tile .arrow {
  transform: translateY(-2.5px);
}

.bf-filepick.over .bf-filepick-call .say b {
  display: none;
}

.bf-filepick.over .bf-filepick-call .say .release {
  display: inline;
}

.bf-filepick.over .bf-filepick-call .or {
  visibility: hidden;
}

/* El foco, el de todo campo: un canto naranja fino y nada más (90.959). */
.bf-filepick:has(> input[type="file"]:focus-visible) {
  border-style: solid;
  border-color: var(--bf-field-focus);
}

/* ── La ficha del fichero ──────────────────────────────────────────────────────────────────────────────────────────── */
.bf-filepick.filled {
  min-height: 0;
  border-style: solid;
}

/* Por ENCIMA del campo invisible pero sin recoger el puntero (lo atraviesa hasta el campo): así el aspa, que sí lo
   recoge, se puede pulsar aunque la entrada haga de la ficha un contexto de apilado. */
.bf-filepick-file {
  position: relative;
  z-index: 3;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 0.875rem;
  padding: 0.875rem 0.75rem 0.875rem 1rem;
  pointer-events: none;
}

.bf-filepick-file[hidden] {
  display: none;
}

.bf-filepick.landed .bf-filepick-file {
  animation: bf-entry-settle var(--bf-dur-entry) var(--bf-ease-out) both;
}

/* La hoja: esquina doblada y, al pie, su extensión. */
.bf-filepick-file .doc {
  position: relative;
  display: grid;
  align-items: end;
  justify-items: center;
  width: 2.5rem;
  height: 3rem;
  padding-bottom: 0.45rem;
  box-sizing: border-box;
  border-radius: 0.45rem;
  background: linear-gradient(170deg, color-mix(in srgb, var(--bf-accent) 22%, var(--bf-field-bg)), color-mix(in srgb, var(--bf-accent) 9%, var(--bf-field-bg)));
  clip-path: polygon(0 0, calc(100% - 0.8rem) 0, 100% 0.8rem, 100% 100%, 0 100%);
}

.bf-filepick-file .doc::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  width: 0.8rem;
  height: 0.8rem;
  border-bottom-left-radius: 0.2rem;
  background: linear-gradient(225deg, transparent 50%, color-mix(in srgb, var(--bf-accent) 40%, var(--bf-field-bg)) 50%);
}

.bf-filepick-file .doc i {
  color: var(--bf-accent-text);
  font-size: 0.625rem;
  font-style: normal;
  font-weight: 800;
  letter-spacing: 0.06em;
  line-height: 1;
}

.bf-filepick-file .meta {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  min-width: 0;
}

.bf-filepick-file .meta b {
  overflow: hidden;
  font-size: var(--bf-text-ui);
  font-weight: 600;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bf-filepick-file .meta small {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  color: var(--bf-muted);
  font-size: var(--bf-text-mini);
}

/* «Cambiar» no es un botón: lo es el pozo entero. Es la pista de que pulsar elige otro. */
.bf-filepick-file .change {
  padding: 0.35rem 0.75rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--bf-accent) 9%, transparent);
  color: var(--bf-accent-text);
  font-size: var(--bf-text-label);
  font-weight: 600;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filepick.filled:hover .bf-filepick-file .change {
  background: color-mix(in srgb, var(--bf-accent) 16%, transparent);
}

/* El aspa, lo único que recoge el puntero por encima del campo invisible. */
.bf-filepick-x {
  position: relative;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--bf-muted);
  cursor: pointer;
  pointer-events: auto;
  transition:
    background-color var(--bf-dur-base) var(--bf-ease-out),
    color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-filepick-x:hover {
  background: var(--bf-hover);
  color: var(--bf-surface-fg);
}

.bf-filepick-x:focus-visible {
  outline: 1px solid var(--bf-field-focus);
  outline-offset: 1px;
}

.bf-filepick-x svg {
  width: 1rem;
  height: 1rem;
}

/* ── El aviso: tipo o peso que el servidor rechazaría ──────────────────────────────────────────────────────────────── */
.bf-filepick.bad {
  border-color: color-mix(in srgb, var(--bf-danger) 60%, var(--bf-field-line));
}

.bf-filepick-err {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.45rem;
  margin: -0.5rem 1rem 1rem;
  padding: 0.45rem 0.75rem;
  border-radius: 0.6rem;
  background: color-mix(in srgb, var(--bf-danger) 9%, transparent);
  color: var(--bf-danger-text);
  font-size: var(--bf-text-label);
  pointer-events: none;
}

.bf-filepick-err[hidden] {
  display: none;
}

@media (max-width: 619.98px) {
  .bf-filepick-file .change {
    display: none;
  }
}

/* ═══ fin del bloque de la chore 90.968 ═══ */

/* ═══ LOS BOTONES DE TERCEROS DEL ACCESO, CON EL HOVER DE «CERRAR SESIÓN» (chore 90.991) ═══════════════════════════════════
   «El hover de Iniciar sesión con Microsoft Entra —por ejemplo, pero en todos— el de cerrar sesión, pero el reflejo más
   disimulado». Al apuntar cualquier proveedor, el gesto de «Cerrar sesión» del centro de control (shell, `.cc-out`;
   chores 90.829/90.836/90.985): el fondo no cambia, el filete se enciende (el del cristal, --bf-edge-line, el mismo que
   el shell), la cruza UN destello de izquierda a derecha —una franja sesgada que nace fuera por la izquierda y sale por
   la derecha en 0,95 s— y la flecha avanza tres píxeles. El destello, más disimulado que el del centro de control: a
   poco más de la mitad de su luz (--bf-door-idp-glint, tokens.css: de día el barrido cálido del acento, de noche
   blanco). El proveedor recorta (`overflow`) para que la franja no asome fuera de su radio. */
.bf-door nav.external .bf-idp {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  transition: border-color 0.2s var(--bf-ease-out);
}

.bf-door nav.external .bf-idp:hover {
  background-image: none;
}

.bf-door nav.external .bf-idp:is(:hover, :focus-visible) {
  border-color: var(--bf-edge-line);
}

.bf-door nav.external .bf-idp::before {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: -30%;
  z-index: -1;
  width: 25%;
  pointer-events: none;
  background: linear-gradient(90deg, transparent, var(--bf-door-idp-glint), transparent);
  transform: skewX(-20deg);
  transition: left 0.95s cubic-bezier(0.22, 1, 0.36, 1);
}

.bf-door nav.external .bf-idp:is(:hover, :focus-visible)::before {
  left: 115%;
}

.bf-door nav.external .bf-idp > .go {
  transition:
    transform 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    color 0.35s var(--bf-ease-out);
}

/* Movimiento reducido: sin viaje, ni de la franja ni de la flecha (ésta ya la frena su regla). */
@media (prefers-reduced-motion: reduce) {
  .bf-door nav.external .bf-idp::before {
    content: none;
  }
}

:root[data-reduce~="motion"] .bf-door nav.external .bf-idp::before {
  content: none;
}

@media (forced-colors: active) {
  .bf-door nav.external .bf-idp::before {
    content: none;
  }
}

/* ═══ fin del bloque de la chore 90.991 ═══ */

/* ═══ LOS CONMUTADORES DEL ACCESO, LOS DEL CENTRO DE CONTROL MÁS TRANSPARENTES (chore 90.992) ══════════════════════════════
   «Los toggles que salen en el login deben ser idénticos a los toggle del centro de control pero más transparentes». El
   tema y el idioma de quien aún no ha entrado se visten como el segmentado del centro de control (shell, `.cc-seg`, con
   la 90.833 encima): una cápsula de píldoras con su filete y el anillo de cristal del buscador encima, 3 px de aire, la
   letra de 10 px en versalita y la elegida como la gota de cristal discreta (velo en degradado, ceja, filete y sombra
   corta) con la tinta del acento. Mismas medidas y tintas que el shell; más transparentes: el carril, a menos de la
   mitad de su velo (el del cuadro va sobre su propio cristal y aquí no hay nada detrás), y la gota, un punto más fina.
   Las entradas escalonadas de la puerta no cambian. */
.bf-prefs {
  --pf-veil: color-mix(in srgb, var(--bf-relief-light) 30%, transparent);
  --pf-hover: color-mix(in srgb, var(--bf-tint-warm) 8%, transparent);
  /* La tinta apagada del .cc-seg del shell (:host de shell.ts), copiada tal cual: si cambia allí, se copia otra vez. */
  --pf-muted: #383d51;
  --pf-g-top: color-mix(in srgb, var(--bf-relief-light) 46%, transparent);
  --pf-g-bot: color-mix(in srgb, var(--bf-relief-light) 40%, transparent);
  --pf-g-hi: color-mix(in srgb, var(--bf-relief-light) 45%, transparent);
  --pf-g-drop: color-mix(in srgb, var(--bf-tint-warm) 8%, transparent);
}

:root[data-theme="dark"] .bf-prefs {
  --pf-veil: color-mix(in srgb, var(--bf-tint-cool) 3.5%, transparent);
  --pf-hover: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
  --pf-muted: #b4bdc7;
  --pf-g-top: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --pf-g-bot: color-mix(in srgb, var(--bf-relief-light) 4.5%, transparent);
  --pf-g-hi: color-mix(in srgb, var(--bf-relief-light) 4.5%, transparent);
  --pf-g-drop: color-mix(in srgb, var(--bf-shadow-ink) 24%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-prefs {
    --pf-veil: color-mix(in srgb, var(--bf-tint-cool) 3.5%, transparent);
    --pf-hover: color-mix(in srgb, var(--bf-tint-cool) 11%, transparent);
    --pf-muted: #b4bdc7;
    --pf-g-top: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --pf-g-bot: color-mix(in srgb, var(--bf-relief-light) 4.5%, transparent);
    --pf-g-hi: color-mix(in srgb, var(--bf-relief-light) 4.5%, transparent);
    --pf-g-drop: color-mix(in srgb, var(--bf-shadow-ink) 24%, transparent);
  }
}

.bf-prefs .group {
  position: relative;
  gap: 0.1875rem;
  padding: 3px;
  border: 0;
  border-radius: 999px;
  background-color: var(--pf-veil);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
}

/* El anillo del buscador, como `.cc-seg::after`: un píxel en degradado que se enciende arriba a la izquierda. */
.bf-prefs .group::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 3;
  padding: 1px;
  border-radius: inherit;
  pointer-events: none;
  background: var(--bf-glass-rim);
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}

.bf-prefs button {
  height: 1.35rem;
  min-width: 1.85rem;
  padding: 0 0.55rem;
  border-radius: 999px;
  color: var(--pf-muted);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  transition:
    background-color 0.2s var(--bf-ease-out),
    color 0.2s var(--bf-ease-out);
}

.bf-prefs button + button {
  border-left: 0;
}

.bf-prefs button:hover:not([aria-current="true"]) {
  background-color: var(--pf-hover);
  color: var(--bf-surface-fg);
}

.bf-prefs button[aria-current="true"] {
  background: linear-gradient(var(--pf-g-top), var(--pf-g-bot));
  box-shadow:
    inset 0 1px 0 var(--pf-g-hi),
    inset 0 0 0 1px var(--bf-edge-line),
    0 1px 1.5px var(--pf-g-drop);
  color: var(--bf-accent);
  font-weight: 700;
}

@media (forced-colors: active) {
  .bf-prefs .group::after {
    content: none;
  }
}

/* ═══ fin del bloque de la chore 90.992 ═══ */

/* ═══ EL FOCO DE TVOS EN LAS TARJETAS DE CRISTAL (chore 90.994) ════════════════════════════════════════════════════════════
   «El hover de las cards con cristal y el efecto 3D debe ser igual pero sin difuminar su contenido; se sigue viendo
   borroso y el efecto 3D no está muy logrado: debe ser como el de las cards de Apple TV (tvOS)». Dos causas del borrón:
   el giro 3D se aplicaba a la vista y a la banda, que llevan el texto (el navegador las pinta como imagen y la
   proyecta), y el paralaje movía las piezas con una TRANSICIÓN que se reiniciaba a cada movimiento del ratón, así que
   vivían en una capa del compositor desplazada a fracciones de píxel; además la vista del Inicio y la baldosa de las
   tarjetas hacían su zoom con `will-change`, que amplía una imagen ya pintada. La 90.984 quitó el giro y el 3D se fue.

   Ahora, como el foco de tvOS, y con el contenido NUNCA girado:
   · LA TARJETA crece un 4 % al apuntarla, con un rebote corto, y se pone por encima de sus vecinas. Es una escala 2D:
     al asentarse, el navegador vuelve a pintar el texto a su tamaño, nítido. Al pulsarla, se hunde un punto.
   · LA PLACA DE CRISTAL gira hacia el puntero (hasta 10°, `--rx`/`--ry` de ui/relief.ts): es el `::before`, que lleva el
     velo de la tarjeta, el reflejo diagonal, el brillo del cursor, el halo, la penumbra del lado contrario y la sombra
     que se corre bajo el giro. Gira con ella el canto (`::after`) y el esmerilado de la banda, que pasa a su propio
     `::before` con el MISMO centro de giro (`--pivot-y`): fondo, canto y banda son un solo plano y nada se separa.
   · EL CONTENIDO (textos, cifras, iconos, el «···» y el asa) se inclina CON la placa, como un solo objeto 3D: cada
     pieza recibe en 2D la deformación que la proyección de la placa hace en su sitio —desplazamiento, escala y sesgo; el
     lado que se acerca crece y el que se aleja encoge— (`--tm`, ui/relief.ts, a cada fotograma). Primero llevó un
     paralaje propio y «lo de dentro se movía más que la card»; luego sólo se desplazaba y «no se veía moverse». Un
     `matrix()` 2D sin transición se pinta con el texto ya transformado —nítido—, no como una imagen proyectada.
   La placa sólo es 3D mientras la tarjeta se mueve (`data-tilting`); en reposo, plana. Con su «···» abierto, la placa
   y el contenido se quedan quietos (90.918). */
@property --tv-on {
  syntax: "<number>";
  inherits: true;
  initial-value: 0;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] {
  --tv-depth: 900px;
  --tv-plate: perspective(var(--tv-depth)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  --tv-band-filter: blur(12px) saturate(1.3);
  --tv-band-ring: 0 0 0 transparent;
  transform: none;
  translate: none;
  scale: 1;
  transition:
    scale 520ms cubic-bezier(0.25, 1.3, 0.5, 1),
    --tv-on 320ms var(--bf-ease-out),
    opacity var(--bf-dur-base) var(--bf-ease-out);
}

.bf-hcard[data-relief][data-relief][data-relief] {
  --tv-band: color-mix(in srgb, var(--bf-surface-bg) 62%, transparent);
}

.bf-home-card[data-relief][data-relief][data-relief] {
  --tv-band: color-mix(in srgb, var(--bf-card-bg) 62%, transparent);
}

/* En General la banda no desenfoca y lleva el canto fino del cristal (19062). */
.bf-pagebox:has(.bf-gen-head) .bf-home-card[data-relief][data-relief][data-relief] {
  --tv-band-filter: var(--bf-card-glass-filter);
  --tv-band-ring: var(--bf-lg-edge);
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:hover {
  --tv-on: 1;
  z-index: 2;
  transform: none;
  translate: none;
  scale: 1.04;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:hover:active {
  scale: 1.02;
  transition-duration: 140ms;
}

/* La placa: el velo y la luz del cristal, y la sombra que se corre bajo el giro (sólo al apuntar). */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]::before {
  --sweep: calc((var(--dx, 0) + var(--dy, 0) * 0.5) / 1.5);
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background-color: var(--card-veil);
  background-image:
    linear-gradient(105deg,
      color-mix(in srgb, var(--lit) 0%, transparent) 40%,
      color-mix(in srgb, var(--lit) calc(var(--sheen) * var(--tv-on) * 100%), transparent) 48%,
      color-mix(in srgb, var(--lit) calc(var(--sheen) * 0.4 * var(--tv-on) * 100%), transparent) 53%,
      color-mix(in srgb, var(--lit) 0%, transparent) 61%),
    radial-gradient(circle 6rem at var(--lx, 50%) var(--ly, 0%),
      color-mix(in srgb, var(--lit) calc(var(--glint) * var(--tv-on) * 100%), transparent) 0%,
      color-mix(in srgb, var(--lit) calc(var(--glint) * 0.66 * var(--tv-on) * 100%), transparent) 18%,
      color-mix(in srgb, var(--lit) calc(var(--glint) * 0.34 * var(--tv-on) * 100%), transparent) 40%,
      color-mix(in srgb, var(--lit) calc(var(--glint) * 0.12 * var(--tv-on) * 100%), transparent) 66%,
      color-mix(in srgb, var(--lit) 0%, transparent) 100%),
    radial-gradient(circle 20rem at var(--lx, 50%) var(--ly, 0%),
      color-mix(in srgb, var(--lit) calc(var(--halo) * var(--tv-on) * 100%), transparent) 0%,
      color-mix(in srgb, var(--lit) calc(var(--halo) * 0.4 * var(--tv-on) * 100%), transparent) 45%,
      color-mix(in srgb, var(--lit) 0%, transparent) 85%),
    linear-gradient(var(--away, 180deg),
      color-mix(in srgb, var(--floor) 0%, transparent) 45%,
      color-mix(in srgb, var(--floor) calc(var(--shade) * var(--lean, 0) * var(--tv-on) * 100%), transparent) 100%);
  background-position: calc(var(--cw, 0px) * (-0.5 - var(--sweep) * 0.6)) 0, 0 0, 0 0, 0 0;
  background-size: calc(var(--cw, 0px) * 2) var(--ch, 0px), auto, auto, auto;
  background-repeat: no-repeat;
  box-shadow:
    calc(var(--dx, 0) * -3px) calc(var(--dy, 0) * -3px + 4px) 10px -4px color-mix(in srgb, var(--floor) calc(0.14 * var(--tv-on) * 100%), transparent),
    calc(var(--dx, 0) * -12px) calc(var(--dy, 0) * -10px + 18px) 36px -14px color-mix(in srgb, var(--floor) calc(var(--drift-shade, 0.3) * var(--tv-on) * 100%), transparent);
  opacity: 1;
  translate: none;
  transform: none;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
  pointer-events: none;
}

/* El canto, cosido a la placa. */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]::after {
  transform: none;
  transition: --rim-on 320ms var(--bf-ease-out);
}

/* La vista y la banda, sin fondo propio ni brillo: los lleva la placa. Y sin giro. */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] > :is(.card-preview, .card-body) {
  background-color: transparent;
  box-shadow: none;
  transform: none;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] > :is(.card-preview, .card-body)::after {
  content: none;
}

/* El esmerilado de la banda pasa a su capa, que gira con la placa (mismo centro: el de la tarjeta). */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] > .card-body {
  border-top-color: transparent;
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] > .card-body::before {
  content: "";
  position: absolute;
  inset: -1px 0 0;
  z-index: -1;
  border-top: 1px solid color-mix(in srgb, var(--bf-border) 70%, transparent);
  border-radius: inherit;
  background-color: var(--tv-band);
  box-shadow: var(--tv-band-ring);
  -webkit-backdrop-filter: var(--tv-band-filter);
  backdrop-filter: var(--tv-band-filter);
  transform: none;
  transform-origin: 50% calc(var(--pivot-y, 50%) + 1px);
  pointer-events: none;
}

/* Mientras se mueve: la placa, el canto y la banda giran juntos. */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]::before,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]::after,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting] > .card-body::before {
  transform: var(--tv-plate);
}

/* El contenido: sin zoom propio (crece la tarjeta entera) y COSIDO a la placa, sin transición: cada pieza se desplaza
   lo que la placa desplaza el punto que tiene debajo (`--tx`/`--ty`, ui/relief.ts). Nada de paralaje propio: «lo de
   dentro de la card se movía más que la card». Lo llevan también el «···» y el asa, que cuelgan de la tarjeta. */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] > :is(.card-preview, .card-body) > *,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:hover > :is(.card-preview, .card-body) > *,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief] .tile {
  transform: none;
  will-change: auto;
  translate: none;
  transition: box-shadow 220ms var(--bf-ease-out), background var(--bf-dur-base) var(--bf-ease-out);
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting] [data-tv-piece] {
  transform: var(--tm, none);
  transform-origin: var(--to, 50% 50%);
}

/* Con su «···» abierto: la placa plana y el contenido quieto (la escala no cambia, el desplegable no se mueve). */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:has(.bf-more[open]) {
  z-index: 6;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]:has(.bf-more[open])::before,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]:has(.bf-more[open])::after,
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]:has(.bf-more[open]) > .card-body::before {
  transform: none;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief][data-tilting]:has(.bf-more[open]) [data-tv-piece] {
  transform: none;
}

/* LAS CIFRAS DEL INICIO, con el mismo foco (desde la 90.766 su hover es EXACTAMENTE el de las tarjetas): crecen, su
   placa —el velo, el canto fino de General y el halo que sigue al puntero— gira, y su texto flota delante, nítido. La
   placa va DEBAJO del texto (z −1 dentro de la cifra aislada): aquí el texto no está posicionado. */
.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief] {
  --tv-depth: 800px;
  --tv-plate: perspective(var(--tv-depth)) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
  --tv-veil: var(--bf-card-veil);
  --floor: var(--bf-relief-floor);
  isolation: isolate;
  background-color: transparent;
  background-image: none;
  box-shadow: none;
  transform: none;
  translate: none;
  scale: 1;
  transition:
    scale 520ms cubic-bezier(0.25, 1.3, 0.5, 1),
    --tv-on 320ms var(--bf-ease-out);
}

:root[data-theme="dark"] .bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief] { --floor: var(--bf-relief-floor); }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief] { --floor: var(--bf-relief-floor); }
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief]:hover {
  --tv-on: 1;
  --tv-veil: var(--bf-card-veil-hover);
  z-index: 2;
  background-color: transparent;
  box-shadow: none;
  transform: none;
  translate: none;
  scale: 1.04;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief]:hover:active {
  scale: 1.02;
  transition-duration: 140ms;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief]::before {
  inset: -1px;
  z-index: -1;
  background-color: var(--tv-veil);
  background-image: radial-gradient(circle 9rem at var(--lx, 50%) var(--ly, 0%),
    rgb(255 255 255 / calc(var(--halo) * var(--tv-on))), transparent 70%);
  box-shadow:
    var(--bf-lg-edge),
    calc(var(--dx, 0) * -6px) calc(var(--dy, 0) * -5px + 10px) 24px -12px color-mix(in srgb, var(--floor) calc(0.22 * var(--tv-on) * 100%), transparent);
  opacity: 1;
  transform: none;
  transition: background-color var(--bf-dur-base) var(--bf-ease-out);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief]::after {
  transform: none;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief][data-tilting]::before,
.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief][data-tilting]::after {
  transform: var(--tv-plate);
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief] > * {
  transform: none;
  translate: none;
}

.bf-pagebox:has(.bf-gen-head) .bf-home-stat[data-relief][data-relief][data-tilting] [data-tv-piece] {
  transform: var(--tm, none);
  transform-origin: var(--to, 50% 50%);
}

/* ═══ fin del bloque de la chore 90.994 ═══ */

/* ═══ EL HOVER DE LOS SECUNDARIOS, SIN NARANJA (petición del usuario, 2026-10-09) ════════════════════════════════════
   «El hover de los botones secundarios no tiene que tener tono anaranjado». La luz que nace arriba al apuntarlos (90.835)
   era un tinte tibio del acento; pasa a luz BLANCA, como la del CTA, más alta en claro porque el cristal del secundario
   ya es casi blanco y al 12 % no se vería. Vale también para `quiet`, que gastaba el mismo tinte. El CTA y el
   destructivo no cambian. */
.bf-btn:not(.primary, .danger) {
  --bf-btn-glow: color-mix(in srgb, var(--bf-relief-light) 60%, transparent);
}

:root[data-theme="dark"] .bf-btn:not(.primary, .danger) {
  --bf-btn-glow: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-btn:not(.primary, .danger) {
    --bf-btn-glow: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
  }
}

/* ═══ fin del bloque del hover de los secundarios ═══ */

/* ═══ EL ASA Y EL «···» DE LAS TARJETAS DE CRISTAL, LOS DE LATANUR EN CUADRADO (petición del usuario, 2026-10-09) ════
   «Los drag and drop y dropdown de las cards de cristal deben ser más similares a los que tienen las card de Latanur,
   pero en un cuadrado y no en un círculo» y «el drag and drop siempre debe estar en la esquina superior izquierda».
   La piel es la de Latanur (`catalog.css`, «El menú y el asa, de cristal en la esquina»): 28 px, el cristal de la
   banda con su esmerilado, su filete interior y una ceja de luz arriba, y la diana crecida a 44 px con un ::before.
   Cambian dos cosas: la forma, un CUADRADO con el canto de un mando (--bf-radius-btn) en vez del disco; y el apuntado,
   que en Latanur pasa al velo de la tarjeta y aquí sería el mismo tono que el reposo en claro: es una capa de luz
   blanca encima, neutra. El «···» se queda arriba a la derecha; el asa pasa ARRIBA A LA IZQUIERDA, en el mismo carril,
   y la marca de esa esquina (el estado de la instancia, la clave del producto) se corre a su derecha y se centra con
   ella en las rejillas que se reordenan. Cuándo se ven no cambia: al apuntar o enfocar la tarjeta. */
.bf-hcard {
  --hc-ctl: 1.75rem;
  --hc-ctl-inset: 0.5rem;
  --hc-ctl-hi: color-mix(in srgb, var(--bf-relief-light) 70%, transparent);
  --hc-ctl-lift: color-mix(in srgb, var(--bf-relief-light) 55%, transparent);
}

:root[data-theme="dark"] .bf-hcard {
  --hc-ctl-hi: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
  --hc-ctl-lift: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-hcard {
    --hc-ctl-hi: color-mix(in srgb, var(--bf-relief-light) 6%, transparent);
    --hc-ctl-lift: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
  }
}

.bf-hcard > .bf-more {
  top: var(--hc-ctl-inset);
  right: var(--hc-ctl-inset);
}

.bf-hcard > .bf-cardgrip {
  top: var(--hc-ctl-inset);
  right: auto;
  left: var(--hc-ctl-inset);
}

.bf-hcard > .bf-more > summary,
.bf-hcard > .bf-cardgrip {
  width: var(--hc-ctl);
  height: var(--hc-ctl);
  border-radius: var(--bf-radius-btn);
  background-color: color-mix(in srgb, var(--bf-surface-bg) 62%, transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line), inset 0 1px 0 var(--hc-ctl-hi);
  transition:
    opacity var(--bf-dur-fast) var(--bf-ease-out),
    background-color var(--bf-dur-fast) var(--bf-ease-out),
    background-image var(--bf-dur-fast) var(--bf-ease-out),
    color var(--bf-dur-fast) var(--bf-ease-out);
}

/* El asa ya es absoluta (es la esquina); el «···» es relativo para que su diana se mida contra él. Los dos son la
   MISMA pieza (pedido expreso: «idéntico, incluso el color»): misma tinta y el glifo a la misma medida. */
.bf-hcard > .bf-more > summary {
  position: relative;
}

.bf-hcard > .bf-more > summary,
.bf-hcard > .bf-cardgrip {
  color: var(--bf-surface-fg);
}

.bf-hcard > .bf-cardgrip > svg {
  width: 0.95rem;
  height: 0.95rem;
}

.bf-hcard > .bf-more > summary::before,
.bf-hcard > .bf-cardgrip::before {
  content: "";
  position: absolute;
  inset: -0.5rem;
}

.bf-hcard > .bf-more > summary:hover,
.bf-hcard > .bf-more[open] > summary,
.bf-hcard > .bf-cardgrip:hover {
  background-color: color-mix(in srgb, var(--bf-surface-bg) 62%, transparent);
  background-image: linear-gradient(var(--hc-ctl-lift), var(--hc-ctl-lift));
  color: var(--bf-surface-fg);
}

/* La marca de la esquina, a la derecha del asa y centrada con ella; el «···» le guarda su sitio a la derecha. */
.bf-hgrid[data-reorder] > .bf-hcard > .pv > .chip,
.bf-hgrid[data-reorder] > .bf-hcard > .pv > .state {
  left: calc(var(--hc-ctl-inset) + var(--hc-ctl) + 0.375rem);
  max-width: calc(100% - 2 * (var(--hc-ctl-inset) + var(--hc-ctl) + 0.375rem));
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-hcard > .bf-more > summary,
  .bf-hcard > .bf-cardgrip {
    background-color: color-mix(in srgb, var(--bf-surface-bg) 92%, transparent);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-hcard > .bf-more > summary,
  .bf-hcard > .bf-cardgrip {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background-color: var(--bf-surface-bg);
  }
}

@media (forced-colors: active) {
  .bf-hcard > .bf-more > summary,
  .bf-hcard > .bf-cardgrip {
    border: 1px solid CanvasText;
    box-shadow: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bf-hcard > .bf-more > summary,
  .bf-hcard > .bf-cardgrip {
    transition: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-hcard > .bf-more > summary, .bf-hcard > .bf-cardgrip) {
  transition: none;
}

/* ═══ fin del bloque del asa y el «···» de las tarjetas de cristal ═══ */

/* ═══ LAS TARJETAS DE INSTANCIAS Y PRODUCTOS, CON LA ANATOMÍA DE LA DE PROCESOS DE LATANUR (petición del usuario, ═══
   2026-10-09) ════════════════════════════════════════════════════════════════════════════════════════════════════════
   «Las cards de instancias y de productos deben ser más como las de procesos de Latanur», con la anatomía completa y el
   logo en la pieza pequeña. Es la receta de `launcher.css` de Latanur con sus números, leída con los tokens de aquí:
     · LA PORTADA (5,25 rem): un dibujo de trazo fino con brillo de neón y unos pocos leds, todo del TONO de la tarjeta,
       en la caja de 320 × 200 pegada arriba y apagado con una máscara hacia la esquina de abajo a la izquierda, que es
       donde van el icono y el texto. En Productos es el recorrido del parque (un led por instancia, del color de su
       salud); en Instancias, un pulso con el led de su estado. Nada de manchas de color: el halo de la vista se va.
     · LA PÍLDORA de cristal arriba, a la derecha del asa y centrada con ella: la categoría con su led o la clave.
     · LA PIEZA DEL ICONO, de cristal y montada sobre la junta de portada y banda, con el trazo luciendo como un led.
     · LA BANDA, que ya no flota sobre la vista sino que la sigue (la tarjeta mide lo que su contenido): el nombre con
       el estado escrito al lado, una línea, y el pie con las cifras y la flecha de ir, que al apuntar la tarjeta se
       vuelve el CTA. Cuadrada como el asa y el «···» (los mandos de estas tarjetas no son discos).
   La vista sigue detrás de toda la tarjeta: la banda esmerilada emborrona el dibujo que le queda debajo. El relieve no
   cambia: la placa gira y cada pieza —la píldora, cada led, el icono— se cose a ella (ui/relief.ts). */
.bf-hcard.bf-hroute {
  --hr-art: 5.25rem;
  --hr-ico: var(--hc-tone);
  --hr-light: var(--bf-relief-light);
  --hr-line: 0.26;
  --hr-dots: 0.36;
  --hr-accent: 0.6;
  --hr-neon: 40%;
  --hr-led-core: 85%;
  --hr-led-halo: 16%;
  --hr-icon-ink: 88%;
  --hr-icon-ink-to: var(--bf-shadow-ink);
  --hr-glass-tile: color-mix(in srgb, var(--bf-relief-light) 60%, transparent);
  --hr-floor-shade: color-mix(in srgb, var(--bf-relief-floor) 30%, transparent);
  display: flex;
  flex-direction: column;
}

:root[data-theme="dark"] .bf-hcard.bf-hroute {
  --hr-line: 0.2;
  --hr-dots: 0.3;
  --hr-accent: 0.5;
  --hr-neon: 70%;
  --hr-led-core: 55%;
  --hr-led-halo: 22%;
  --hr-icon-ink: 70%;
  --hr-icon-ink-to: var(--bf-relief-light);
  --hr-glass-tile: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
  --hr-floor-shade: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-hcard.bf-hroute {
    --hr-line: 0.2;
    --hr-dots: 0.3;
    --hr-accent: 0.5;
    --hr-neon: 70%;
    --hr-led-core: 55%;
    --hr-led-halo: 22%;
    --hr-icon-ink: 70%;
    --hr-icon-ink-to: var(--bf-relief-light);
    --hr-glass-tile: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
    --hr-floor-shade: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
  }
}

/* En Instancias el tono es el ESTADO (lo pinta el dibujo y su led); el icono lleva el color de su producto. */
.bf-inst.bf-hcard.bf-hroute {
  --hr-ico: var(--pt, var(--bf-muted));
}

.bf-hcard.bf-hroute:hover {
  --hr-boost: 1.45;
}

/* La vista, detrás de toda la tarjeta y sin halo de color. */
.bf-hcard.bf-hroute > .pv {
  position: absolute;
  inset: 0;
  display: block;
  aspect-ratio: auto;
  padding: 0;
  border-radius: inherit;
}

.bf-hcard.bf-hroute > .pv::before {
  content: none;
}

/* El dibujo y la capa de sus leds, en la misma caja de 320 × 200. */
.bf-hcard.bf-hroute > .pv > .route,
.bf-hcard.bf-hroute > .pv > .bf-dotline {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  width: 100%;
  max-width: none;
  aspect-ratio: 320 / 200;
  padding: 0;
  color: var(--hc-tone);
  pointer-events: none;
  /* El #000 de la máscara no es un color: solo cuenta su opacidad. */
  -webkit-mask-image: radial-gradient(120% 115% at 100% 0%, #000 30%, transparent 78%);
  mask-image: radial-gradient(120% 115% at 100% 0%, #000 30%, transparent 78%);
}

.bf-hcard.bf-hroute > .pv > .route > svg {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;
  filter: drop-shadow(0 0 2.5px color-mix(in srgb, var(--hc-tone) var(--hr-neon), transparent));
}

.bf-hcard.bf-hroute > .pv > .route path {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.25;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-opacity: calc(var(--hr-accent) * var(--hr-boost, 1));
  vector-effect: non-scaling-stroke;
  transition: stroke-opacity 300ms var(--bf-ease-out);
}

/* Sin instancias todavía, el trazo se queda de puntos: se ve que ahí falta algo, que es la verdad. */
.bf-hcard.bf-hroute > .pv > .route path.none {
  stroke-width: 2.2;
  stroke-dasharray: 0 15;
  stroke-opacity: calc(var(--hr-dots) * var(--hr-boost, 1));
}

/* EL LED: núcleo claro y un halo apretado de su color. Tenue a propósito: es un detalle de la portada. Su sitio
   sobre el trazo llega de la vista en `--x` y `--y`, en tanto por ciento de la tarjeta. */
.bf-hcard.bf-hroute .pt {
  position: absolute;
  left: var(--x);
  top: var(--y);
  display: block;
  width: 5px;
  height: 5px;
  margin: -2.5px 0 0 -2.5px;
}

.bf-hcard.bf-hroute .pt.sm {
  width: 3.5px;
  height: 3.5px;
  margin: -1.75px 0 0 -1.75px;
}

.bf-hcard.bf-hroute .pt > i {
  --led: var(--hc-tone);
  display: block;
  width: 100%;
  height: 100%;
  border-radius: 50%;
  background: color-mix(in srgb, var(--led) var(--hr-led-core), var(--hr-light));
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--led) 55%, transparent),
    0 0 5px 1px color-mix(in srgb, var(--led) 45%, transparent),
    0 0 14px 3px color-mix(in srgb, var(--led) var(--hr-led-halo), transparent);
  transition: transform 300ms var(--bf-ease-out);
}

/* En Productos, cada led del color de la salud de SU instancia. */
.bf-hcard.bf-hroute .bf-dotline .pt > i {
  --led: var(--bf-success);
}

.bf-hcard.bf-hroute .bf-dotline .pt > i.w {
  --led: var(--bf-warning);
}

.bf-hcard.bf-hroute .bf-dotline .pt > i.b {
  --led: var(--bf-danger);
}

.bf-hcard.bf-hroute .bf-dotline .pt > i.o {
  --led: var(--bf-muted);
}

.bf-hcard.bf-hroute:hover .pt > i {
  transform: scale(1.3);
}

/* LA PÍLDORA de cristal: a la derecha del asa en las rejillas que se reordenan, centrada con ella. */
.bf-hcard.bf-hroute > .pv > .pill {
  position: absolute;
  top: calc(var(--hc-ctl-inset) + (var(--hc-ctl) - 1.6rem) / 2);
  left: var(--hc-ctl-inset);
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  max-width: calc(100% - 2 * (var(--hc-ctl-inset) + var(--hc-ctl) + 0.375rem));
  height: 1.6rem;
  padding: 0 0.7rem;
  overflow: hidden;
  border-radius: 999px;
  background-color: color-mix(in srgb, var(--bf-surface-bg) 62%, transparent);
  -webkit-backdrop-filter: blur(10px) saturate(1.3);
  backdrop-filter: blur(10px) saturate(1.3);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line), inset 0 1px 0 var(--hc-ctl-hi);
  font-size: var(--bf-text-mini);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--bf-muted);
}

.bf-hgrid[data-reorder] > .bf-hcard.bf-hroute > .pv > .pill {
  left: calc(var(--hc-ctl-inset) + var(--hc-ctl) + 0.375rem);
}

/* La clave es un identificador: se escribe como es, sin versalitas. */
.bf-hcard.bf-hroute > .pv > .pill.key {
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: none;
}

.bf-hcard.bf-hroute > .pv > .pill > i {
  display: block;
  flex: none;
  width: 0.38rem;
  height: 0.38rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--hr-ico) var(--hr-led-core), var(--hr-light));
  box-shadow:
    0 0 4px 1px color-mix(in srgb, var(--hr-ico) 45%, transparent),
    0 0 9px 2px color-mix(in srgb, var(--hr-ico) var(--hr-led-halo), transparent);
}

/* LA PIEZA DEL ICONO, montada sobre la junta: 1,6 rem por encima de la banda y el resto dentro. No es diana: debajo
   está el enlace de la tarjeta. */
.bf-hcard.bf-hroute > .ico {
  position: absolute;
  top: calc(var(--hr-art) - 1.6rem);
  left: 0.95rem;
  z-index: 3;
  display: grid;
  place-items: center;
  width: 2.85rem;
  height: 2.85rem;
  overflow: hidden;
  border-radius: 0.85rem;
  background: var(--hr-glass-tile);
  -webkit-backdrop-filter: blur(12px) saturate(1.5);
  backdrop-filter: blur(12px) saturate(1.5);
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--hr-ico) 42%, transparent),
    inset 0 1px 0 var(--hc-ctl-hi),
    0 0 14px -5px color-mix(in srgb, var(--hr-ico) 55%, transparent),
    0 2px 6px -3px var(--hr-floor-shade);
  color: color-mix(in srgb, var(--hr-ico) var(--hr-icon-ink), var(--hr-icon-ink-to));
  font-size: var(--bf-text-body);
  font-weight: 700;
  letter-spacing: 0.02em;
  pointer-events: none;
  transition: box-shadow 300ms var(--bf-ease-out);
}

.bf-hcard.bf-hroute > .ico > svg {
  width: 1.35rem;
  height: 1.35rem;
  stroke-width: 1.8;
  filter:
    drop-shadow(0 0 2px color-mix(in srgb, var(--hr-ico) var(--hr-neon), transparent))
    drop-shadow(0 0 6px color-mix(in srgb, var(--hr-ico) calc(var(--hr-neon) * 0.5), transparent));
}

/* El logo de marca, entero dentro de la pieza. */
.bf-hcard.bf-hroute > .ico.logo {
  padding: 0.4rem;
}

.bf-hcard.bf-hroute > .ico.logo > img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.bf-hcard.bf-hroute:hover > .ico {
  box-shadow:
    inset 0 0 0 1px color-mix(in srgb, var(--hr-ico) 60%, transparent),
    inset 0 1px 0 var(--hc-ctl-hi),
    0 0 18px -4px color-mix(in srgb, var(--hr-ico) 70%, transparent),
    0 2px 6px -3px var(--hr-floor-shade);
}

/* LA BANDA, detrás de la portada y del alto de lo que lleva; arriba deja aire para la pieza del icono. */
.bf-hcard.bf-hroute > .band {
  position: relative;
  inset: auto;
  flex: 1 0 auto;
  justify-content: flex-start;
  gap: 0;
  min-height: 0;
  margin-top: var(--hr-art);
  padding: 1.75rem 0.95rem 0.75rem;
}

.bf-hcard.bf-hroute > .band > .head {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  min-width: 0;
}

.bf-hcard.bf-hroute > .band > .head > .nm {
  flex: 1;
  min-width: 0;
  font-size: var(--bf-text-lead);
  letter-spacing: -0.01em;
}

/* LA BANDA DEJA PASAR EL CLIC al enlace de fondo de la tarjeta (`.hit`, que la cubre entera): pulsar en cualquier sitio
   abre la ficha. No bastaba con que el enlace del nombre estirase su capa a la banda, como cuando iba suelto en ella:
   mientras el ratón está encima, el relieve le pone al nombre su `transform` y la capa queda encerrada en él, y la
   banda —media tarjeta— se tragaba el clic. Sólo recogen el puntero lo que es enlace (el nombre, la licencia que lleva a
   arreglarla) y lo que tiene algo que decir al apuntarlo (las cifras y pastillas con su título). La flecha no: es la
   tarjeta entera. */
.bf-hcard.bf-hroute > .band {
  pointer-events: none;
}

.bf-hcard.bf-hroute > .band :is(a, .ft > *) {
  pointer-events: auto;
}

.bf-hcard.bf-hroute > .band .ft > .go {
  pointer-events: none;
}

.bf-hcard.bf-hroute > .band > .head > a.nm:focus-visible {
  outline: none;
}

.bf-hcard.bf-hroute:has(> .band > .head > a.nm:focus-visible) {
  outline: 2px solid var(--bf-accent);
  outline-offset: 2px;
}

/* EL ESTADO: led y palabra, apagada. Cede ancho antes que el nombre y se recorta si no cabe. */
.bf-hcard.bf-hroute > .band > .head > :is(.state, .bf-hstatus) {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  flex: 0 1 auto;
  min-width: 0;
  max-width: 55%;
  overflow: hidden;
  font-size: var(--bf-text-mini);
  font-weight: 600;
  white-space: nowrap;
  text-overflow: ellipsis;
  color: var(--bf-muted);
}

.bf-hcard.bf-hroute > .band > .head > .state > b {
  font-weight: 600;
  color: inherit;
}

.bf-hcard.bf-hroute > .band > .head > .state > [data-health-since] {
  min-width: 0;
  overflow: hidden;
  font-weight: 400;
  text-overflow: ellipsis;
}

.bf-hcard.bf-hroute > .band > .head > .state > [data-health-since]:empty {
  display: none;
}

.bf-hcard.bf-hroute > .band > .head .lamp {
  flex: none;
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  background: color-mix(in srgb, var(--hc-tone) var(--hr-led-core), var(--hr-light));
  box-shadow:
    0 0 4px 1px color-mix(in srgb, var(--hc-tone) 85%, transparent),
    0 0 10px 2px color-mix(in srgb, var(--hc-tone) 40%, transparent);
}

.bf-hcard.bf-hroute > .band > .sub {
  margin-top: 0.2rem;
}

/* EL PIE: las cifras a la izquierda y la flecha a la derecha, siempre al fondo de la banda —las tarjetas de una fila
   lo tienen a la misma altura—. */
.bf-hcard.bf-hroute > .band > .ft {
  flex-wrap: nowrap;
  min-height: 1.75rem;
  margin-top: auto;
  padding-top: 0.55rem;
}

/* La licencia ya no empuja lo demás a la derecha: allí sólo va la flecha. */
.bf-hcard.bf-hroute .ft > .lic {
  min-width: 0;
  margin-right: 0;
}

.bf-hcard.bf-hroute .go {
  display: grid;
  place-items: center;
  flex: none;
  width: 1.75rem;
  height: 1.75rem;
  margin-left: auto;
  border-radius: var(--bf-radius-btn);
  background-color: color-mix(in srgb, var(--bf-surface-fg) 6%, transparent);
  box-shadow: inset 0 0 0 1px var(--bf-edge-line);
  color: var(--bf-surface-fg);
  transition:
    background-color 220ms var(--bf-ease-out),
    color 220ms var(--bf-ease-out),
    translate 220ms var(--bf-ease-out),
    box-shadow 220ms var(--bf-ease-out);
}

.bf-hcard.bf-hroute .go > svg {
  width: 0.875rem;
  height: 0.875rem;
}

/* Al apuntar la tarjeta, el CTA: su color y su tinta, SIN el led (el halo de su color que echa debajo un CTA al
   apuntarlo; pedido expreso, 2026-10-09: «evitar led en los CTA de las cards»). */
.bf-hcard.bf-hroute:hover .go {
  translate: 2px 0;
  background-color: var(--bf-cta);
  box-shadow: none;
  color: var(--bf-on-cta);
}

/* Cuando el cristal no está o no se quiere. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .bf-hcard.bf-hroute > .pv > .pill {
    background-color: color-mix(in srgb, var(--bf-surface-bg) 92%, transparent);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .bf-hcard.bf-hroute > .pv > .pill,
  .bf-hcard.bf-hroute > .ico {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
}

/* Con colores forzados no hay brillos. */
@media (forced-colors: active) {
  .bf-hcard.bf-hroute > .pv > .route > svg,
  .bf-hcard.bf-hroute > .ico > svg {
    filter: none;
  }

  .bf-hcard.bf-hroute .pt > i,
  .bf-hcard.bf-hroute > .pv > .pill > i,
  .bf-hcard.bf-hroute > .band > .head .lamp {
    box-shadow: none;
  }

  .bf-hcard.bf-hroute > .ico,
  .bf-hcard.bf-hroute .go,
  .bf-hcard.bf-hroute > .pv > .pill {
    border: 1px solid CanvasText;
    box-shadow: none;
  }
}

/* Con menos movimiento, el apuntado se queda en el cambio de tono. */
@media (prefers-reduced-motion: reduce) {
  .bf-hcard.bf-hroute .go,
  .bf-hcard.bf-hroute .pt > i,
  .bf-hcard.bf-hroute > .ico {
    transition: none;
  }

  .bf-hcard.bf-hroute:hover .go {
    translate: none;
  }

  .bf-hcard.bf-hroute:hover .pt > i {
    transform: none;
  }
}

:root[data-reduce~="motion"] :is(.bf-hcard.bf-hroute .go, .bf-hcard.bf-hroute .pt > i, .bf-hcard.bf-hroute > .ico) {
  transition: none;
}

:root[data-reduce~="motion"] .bf-hcard.bf-hroute:hover .go {
  translate: none;
}

:root[data-reduce~="motion"] .bf-hcard.bf-hroute:hover .pt > i {
  transform: none;
}

/* ═══ fin del bloque de la anatomía de la tarjeta de procesos ═══ */

/* ═══ LA FILA APUNTADA DE UN DESPLEGABLE, PLANA (petición del usuario, 2026-10-09) ═══════════════════════════════════
   «El seleccionado dentro de los dropdown no tiene que tener gradiente». Las filas del «···» son `.bf-btn` y heredaban
   la luz que nace arriba al apuntar un botón (90.835, el ::after): un degradado radial encima del velo de la fila. La
   fila no es un botón: apuntada o con el foco, sólo el velo liso (la destructiva ya lo era desde la 90.909). */
.bf-more .pop .bf-btn::after {
  display: none;
}

.bf-more .pop .bf-btn:is(:hover, :focus-visible):not(:disabled) {
  background-image: none;
  box-shadow: none;
}

/* ═══ fin del bloque de la fila apuntada ═══ */

/* ═══ TODO LO DE UN FILTRO, CON EL RADIO DE UN CAMPO (petición del usuario, 2026-10-09) ══════════════════════════════
   «Todo lo que sale dentro de un filtro debe tener el border radius del resto de inputs de la plataforma». Las barras
   de filtros eran de cápsulas (999 px) o llevaban el radio de un mando (0,5 rem) o uno propio (Auditoría, 0,6 rem), al
   lado de campos de 0,4 rem. Ahora todo lo que hay en ellas lleva el radio de un campo (--bf-field-radius): en
   Usuarios (la caja de filtros: el buscador, las cápsulas de los desplegables, y en la cabecera la baldosa del embudo,
   el recuento y el botón de plegar), en Instancias (el desplegable y el buscador) y en Auditoría (el buscador y las
   pastillas). Lo que va DENTRO de una de esas piezas —las cuentas, el aspa de limpiar— lleva ese radio menos el hueco
   que lo separa del canto, para que las dos curvas vayan a la par.
   SALVO LOS CONMUTADORES (pedido expreso, el mismo día: «debe ser circular»): el estado —«Todas | Vivas…», «activos |
   de baja…»— y la vista de Auditoría —«Cronológica | Por sesión»— son el carril con gota de todos los conmutadores de
   la casa («Tarjetas | Tabla») y van redondos como ellos: carril, gota, opciones y cuentas. */
.bf-filter,
.bf-fsearch,
.bf-field-search,
.bf-aud-search,
.bf-aud-pill,
.bf-filterbox-count,
.bf-filterbox .bf-filterbox-body .bf-filter:has(> select),
.bf-filterbox .bf-filterbox-body .bf-filter > select,
.bf-filterbox .bf-filterbox-body .bf-field-search {
  border-radius: var(--bf-field-radius);
}

/* Dentro, a 2 px del canto. */
.bf-aud-pill > b.n,
.bf-fsearch > button,
.bf-field-search[data-filled="true"] > button {
  border-radius: calc(var(--bf-field-radius) - 0.125rem);
}

/* ═══ fin del bloque de los filtros ═══ */

/* ═══ EL ESTADO DE LOS FILTROS, COMO LOS DEMÁS CONMUTADORES (petición del usuario, 2026-10-09) ═════════════════════════
   «El toggle de filtros de estado debe tener los estilos del resto de toggles». Su carril ya es el de «Tarjetas | Tabla»
   también en la caja de filtros (el bloque de la 90.923 lo metía en una cápsula y le quitaba el velo y el filete); aquí,
   lo que quedaba: sus opciones con el mismo aire a los lados que las de los demás (0,9375 rem), y no el asimétrico que
   dejaba sitio al punto de color. El punto hace de icono, como el de «Tarjetas». Y redondo como ellos: los conmutadores
   son la excepción al radio de campo de los filtros (bloque anterior). */
.bf-fchips.bf-fchips > .row > label {
  padding-inline: 0.9375rem;
}

/* ═══ fin del bloque del estado de los filtros ═══ */

/* ═══ EL EMBUDO DE LOS FILTROS, SIN BALDOSA (petición del usuario, 2026-10-09) ═════════════════════════════════════════
   «El icono de los menús de filtro no debe estar contenido en un cuadrado y ser un pelín más grande que la mayúscula F
   de Filtros». Fuera la baldosa de la 90.923 (relleno, velo y radio): el embudo va suelto, en la tinta apagada, y
   cuando hay filtros puestos se enciende con la tinta del acento, como antes, sin fondo. Su caja es 1,05 veces la letra
   del rótulo (--bf-text-ui), y el dibujo ocupa 18 de sus 24 unidades: unos 10,5 px de alto contra los 9 de la «F». Más
   cerca del rótulo que el hueco general de la cabecera, como un icono de botón.
   ALINEADO CON LA PALABRA («y debe estar alineado el icono con la palabra»): centrar la caja contra la del rótulo lo
   dejaba un píxel por encima del centro de sus mayúsculas (medido: la caja de línea guarda sitio a los descendentes,
   así que su centro cae por encima del de la «F»). Baja ese píxel, en rem para que siga a la raíz fluida. */
.bf-filterbox-head > svg:first-child,
.bf-filterbox:has(.bf-filterbox-count:not([hidden])) > .bf-filterbox-head > svg:first-child {
  box-sizing: content-box;
  width: calc(var(--bf-text-ui) * 1.05);
  height: calc(var(--bf-text-ui) * 1.05);
  margin-right: -0.25rem;
  padding: 0;
  border-radius: 0;
  background-color: transparent;
  translate: 0 0.0625rem;
}

/* EL GALÓN DE PLEGAR, TAMBIÉN SIN CAJA (pedido expreso: «el toggle tampoco debe tener un cuadrado que lo contenga»):
   sólo la flecha, en la tinta apagada, que se aviva al apuntar la cabecera y gira al abrir. Conserva su caja de
   1,625 rem como diana, transparente. */
.bf-filterbox-chev,
.bf-filterbox-head:hover .bf-filterbox-chev {
  background-color: transparent;
}

.bf-filterbox-chev {
  color: var(--bf-muted);
}

.bf-filterbox-head:hover .bf-filterbox-chev {
  color: var(--bf-surface-fg);
}

/* ═══ fin del bloque del embudo ═══ */

/* ═══ EL RELIEVE, MÁS DISIMULADO (petición del usuario, 2026-10-09) ═════════════════════════════════════════════════
   «El movimiento de las cards de cristal con hover debe ser un poco menos exagerado, sobre todo al hacer hover en sus
   esquinas». El giro lo baja ui/relief.ts (7° y la esquina acotada al círculo); aquí la tarjeta crece algo menos al
   apuntarla: del 4 % al 3 %, y al pulsar del 2 % al 1,5 %. */
:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:hover {
  scale: 1.03;
}

:is(.bf-home-card, .bf-hcard)[data-relief][data-relief][data-relief]:hover:active {
  scale: 1.015;
}

/* ═══ fin del bloque del relieve ═══ */

/* ═══ EL DEGRADADO DE LOS BOTONES, MÁS DISIMULADO (petición del usuario, 2026-10-09) ════════════════════════════════
   «El gradiente de los botones secundarios debe ser más disimulado, al igual que los que son rojos». El velo de cristal
   del secundario y el del destructivo iban de mucha luz arriba a poca abajo (en claro, blanco 0,72 → 0,42; el rojo,
   24 % → 11 %). Se acercan los dos extremos SIN mover el medio, para que el botón pese lo mismo y sólo se note menos el
   degradado; igual en su hover. El canto de cristal, la ceja y el pulsado no cambian. */
.bf-btn {
  --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 62%, transparent);
  --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 50%, transparent);
  --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 80%, transparent);
  --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 68%, transparent);
}

:root[data-theme="dark"] .bf-btn {
  --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
  --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
  --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 11%, transparent);
  --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-btn {
    --bf-b2-top: color-mix(in srgb, var(--bf-relief-light) 7%, transparent);
    --bf-b2-bottom: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
    --bf-b2-top-on: color-mix(in srgb, var(--bf-relief-light) 11%, transparent);
    --bf-b2-bottom-on: color-mix(in srgb, var(--bf-relief-light) 8%, transparent);
  }
}

@supports (background-clip: border-area) {
  .bf-btn.danger:not(.solid):not(.bf-more .pop *) {
    background-image:
      linear-gradient(135deg, color-mix(in srgb, var(--dz) 80%, transparent) 0%, color-mix(in srgb, var(--dz) 30%, transparent) 30%,
        color-mix(in srgb, var(--dz) 22%, transparent) 64%, color-mix(in srgb, var(--dz) 55%, transparent) 100%),
      linear-gradient(180deg, color-mix(in srgb, var(--dz) 20%, transparent), color-mix(in srgb, var(--dz) 15%, transparent));
  }

  .bf-btn.danger:not(.solid):not(.bf-more .pop *)::after {
    background:
      radial-gradient(120% 150% at 50% -30%, var(--bf-btn-glow), transparent 70%),
      linear-gradient(180deg, color-mix(in srgb, var(--dz) 13%, transparent), color-mix(in srgb, var(--dz) 11%, transparent));
  }
}

/* ═══ fin del bloque del degradado de los botones ═══ */

/* ═══ LOS BOTONES APAGADOS, PARTIENDO DEL ENCENDIDO (petición del usuario, 2026-10-16) ═══════════════════════════════
   «Debes realizar el estilo de los botones disabled, tanto CTA y secundarios en ambos temas», y a la segunda, «más
   elaborados y sin borde, partiendo del estilo de los activados». El apagado de antes se había quedado atrás de los
   botones nuevos: el CTA conservaba su naranja macizo (en claro casi no se distinguía del vivo) y de noche le caía la
   tinta blanca de los secundarios; el secundario seguía con todo su cristal debajo y además un filete, así que apagado
   resaltaba MÁS que encendido. Ahora cada uno conserva su material —el CTA su relleno naranja con la luz arriba, el
   secundario su velo en degradado con la ceja—, rebajado a algo menos de la mitad, SIN canto ni filete, con una sombra
   mínima debajo y la tinta a media fuerza. Quietos: ni se levantan, ni brillan, ni adelantan el icono. Igual con
   `disabled` que con `aria-disabled="true"` (los enlaces con forma de botón). Ocupado no es apagado (S-9): con
   aria-busy se quedan como estaban. El destructivo y `quiet` no cambian en esta tanda. */
.bf-btn {
  --bf-off-top: color-mix(in srgb, var(--bf-relief-light) 46%, transparent);
  --bf-off-bottom: color-mix(in srgb, var(--bf-relief-light) 30%, transparent);
  --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 70%, transparent);
  --bf-off-drop: color-mix(in srgb, var(--bf-shadow-ink) 7%, transparent);
  --bf-off-ink: color-mix(in srgb, var(--bf-surface-fg) 42%, transparent);
}

/* El CTA apagado SIGUE SIENDO NARANJA: es la acción principal que espera a algo (un formulario a medias), y si se
   volviera gris la pantalla perdería cuál es hasta que se encienda. */
.bf-btn.primary {
  --bf-off-top: color-mix(in srgb, var(--bf-cta) 50%, transparent);
  --bf-off-bottom: color-mix(in srgb, var(--bf-cta) 34%, transparent);
  --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 45%, transparent);
  --bf-off-drop: transparent;
  --bf-off-ink: color-mix(in srgb, var(--bf-on-cta) 46%, transparent);
}

/* De noche el velo es de una luz mucho más baja, como el encendido, y la tinta del CTA pasa a su naranja legible: la
   letra oscura sobre un naranja a media fuerza encima del fondo oscuro se perdería. */
:root[data-theme="dark"] .bf-btn {
  --bf-off-top: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
  --bf-off-bottom: color-mix(in srgb, var(--bf-relief-light) 2.5%, transparent);
  --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
  --bf-off-drop: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
  --bf-off-ink: color-mix(in srgb, var(--bf-surface-fg) 38%, transparent);
}

:root[data-theme="dark"] .bf-btn.primary {
  --bf-off-top: color-mix(in srgb, var(--bf-cta) 30%, transparent);
  --bf-off-bottom: color-mix(in srgb, var(--bf-cta) 18%, transparent);
  --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
  --bf-off-drop: transparent;
  --bf-off-ink: color-mix(in srgb, var(--bf-accent-text) 72%, transparent);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .bf-btn {
    --bf-off-top: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
    --bf-off-bottom: color-mix(in srgb, var(--bf-relief-light) 2.5%, transparent);
    --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 5%, transparent);
    --bf-off-drop: color-mix(in srgb, var(--bf-shadow-ink) 30%, transparent);
    --bf-off-ink: color-mix(in srgb, var(--bf-surface-fg) 38%, transparent);
  }

  :root:not([data-theme="light"]) .bf-btn.primary {
    --bf-off-top: color-mix(in srgb, var(--bf-cta) 30%, transparent);
    --bf-off-bottom: color-mix(in srgb, var(--bf-cta) 18%, transparent);
    --bf-off-hi: color-mix(in srgb, var(--bf-relief-light) 10%, transparent);
    --bf-off-drop: transparent;
    --bf-off-ink: color-mix(in srgb, var(--bf-accent-text) 72%, transparent);
  }
}

/* El doble .bf-btn le da el peso del cristal de los secundarios (`.bf-btn:not(…):not(.bf-more .pop *)`), que si no
   seguiría pintando su canto debajo. */
.bf-btn.bf-btn:is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]) {
  background-color: transparent;
  background-image: linear-gradient(180deg, var(--bf-off-top), var(--bf-off-bottom));
  background-origin: border-box;
  background-clip: border-box;
  border-color: transparent;
  box-shadow: inset 0 1px 0 var(--bf-off-hi), 0 1px 2px var(--bf-off-drop);
  color: var(--bf-off-ink);
  transform: none;
  cursor: not-allowed;
}

.bf-btn.bf-btn:is(:disabled, [aria-disabled="true"]):not([aria-busy="true"])::after {
  display: none;
}

.bf-btn.bf-btn:is(:disabled, [aria-disabled="true"]):not([aria-busy="true"]) > svg {
  transform: none;
}

/* ═══ fin del bloque de los botones apagados ═══ */

/* ═══ PORCELANA: LAS TARJETAS SE LEVANTAN DEL SUELO (petición del usuario, 2026-10-16) ════════════════════════════════
   «Se nota poco el cambio en local del modo claro, menos de lo que se ve en el mockup». Con el velo al 30 % del tema
   claro Porcelana, la tarjeta es casi del tono del suelo; en el mockup la separaba además una sombra corta y otra larga
   en la tinta pizarra (--bf-card-glass-drop), y aquí sólo la llevaba `.bf-card`. Las demás piezas que son tarjeta —las
   del Inicio, la columna y las celdas de la ficha de instancia, las de producto— la toman también. En oscuro el token
   vale nada y todo queda como estaba. */
.bf-pagebox:has(.bf-gen-head) :is(.bf-card, .bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk,
  .bf-gen-facts > li) {
  box-shadow: var(--bf-lg-edge), var(--bf-card-glass-drop);
}

.bf-ific :is(.bf-iside, .bf-icell),
.bf-ific .bf-ipane .bf-form-row,
.bf-pcard {
  box-shadow: var(--bf-card-glass-drop);
}

/* ═══ fin del bloque de Porcelana ═══ */

/* ═══ PORCELANA: EL CANTO DE CRISTAL DE LAS PIEZAS (petición del usuario, 2026-10-16) ══════════════════════════════════
   «El efecto cristal debe ser igual que el modo oscuro; lo que no me gusta es que hay poco contraste del fondo con los
   items (no exagerado), quizás con un borde fino cristal en los items de las vistas». Mismo material que el oscuro (el
   velo y su desenfoque no cambian); lo que cambia es el CANTO: en vez del filete liso, el de cristal en degradado
   (--bf-item-rim) en el área del borde, como el buscador y los secundarios. Sólo en claro: el oscuro conserva su filete.
   Sin border-area, el filete de siempre. */
@supports (background-clip: border-area) {
  :root[data-theme="light"] :is(.bf-card, .bf-pcard, .bf-ific .bf-iside, .bf-ific .bf-icell,
    .bf-pagebox:has(.bf-gen-head) :is(.bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk, .bf-gen-facts > li),
    .bf-home-stat.bf-home-stat.bf-home-stat, .bf-hcard.bf-hcard.bf-hcard) {
    border-color: transparent;
    background-image: var(--bf-item-rim);
    background-origin: border-box;
    background-clip: border-area;
  }
}

@media (prefers-color-scheme: light) {
  @supports (background-clip: border-area) {
    :root:not([data-theme="dark"]) :is(.bf-card, .bf-pcard, .bf-ific .bf-iside, .bf-ific .bf-icell,
      .bf-pagebox:has(.bf-gen-head) :is(.bf-prof-hero, .bf-home-hero, .bf-home-stat, .bf-home-desk, .bf-gen-facts > li),
    .bf-home-stat.bf-home-stat.bf-home-stat, .bf-hcard.bf-hcard.bf-hcard) {
      border-color: transparent;
      background-image: var(--bf-item-rim);
      background-origin: border-box;
      background-clip: border-area;
    }
  }
}

/* ═══ fin del bloque del canto de cristal ═══ */

/* ═══ EL AVISO DE LO QUE DESTRUYE (petición del usuario, 2026-10-15) ═══════════════════════════════════════════════════
   «El modal de retirar debería ser un aviso más orgánico para el usuario, y tener el icono de aviso»: la confirmación
   en tono peligroso (ts/ui/confirm.ts añade `.bf-alert`) deja de ser cabecera + cuerpo + pie partidos por filetes. Es
   una sola pieza: el triángulo «warn» del shell en su baldosa redonda del rojo suave, el título a su lado, el texto
   colgando bajo el título y los dos botones al pie sin pelo encima. */
.bf-modal.bf-alert {
  width: 440px;
}

.bf-modal.bf-alert > header {
  display: flex;
  align-items: center;
  gap: 0.875rem;
  padding: 1.25rem 1.25rem 0.25rem;
  border-bottom: 0;
}

.bf-modal.bf-alert > header h3 {
  font-size: var(--bf-text-lead);
}

.bf-alert-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 999px;
  background: var(--bf-danger-soft);
  color: var(--bf-danger-text);
}

.bf-alert-icon > svg {
  width: 1.25rem;
  height: 1.25rem;
}

/* El texto, alineado con el título: el sangrado es la baldosa más su hueco. */
.bf-modal.bf-alert .in {
  padding: 0 1.25rem 1.25rem 4.625rem;
  color: var(--bf-muted);
  line-height: 1.5;
}

.bf-modal.bf-alert footer {
  border-top: 0;
  padding: 0 1.25rem 1.25rem;
}

@media (max-width: 479.98px) {
  /* En el teléfono el sangrado se come el renglón: el texto vuelve al canto. */
  .bf-modal.bf-alert .in {
    padding-left: 1.25rem;
  }
}

/* ═══ fin del bloque del aviso ═══ */

/* ═══ LOS FILTROS DE LOS ROLES, EN LA CABECERA (petición del usuario, 2026-10-15) ═════════════════════════════════════
   El conmutador del estado, la lupa y el alta en la misma fila que el título del producto. La lupa, estrecha como la de
   Organización; por debajo del corte del teléfono se parte en renglones y la lupa se estira. */
.bf-rolebar {
  flex-wrap: wrap;
  align-items: center;
}

.bf-rolebar > form {
  display: contents;
}

.bf-rolebar .bf-fsearch {
  width: 14rem;
  height: 2rem;
}

@media (max-width: 619.98px) {
  .bf-rolebar .bf-fsearch {
    width: 100%;
    order: 1;
  }
}

/* ═══ fin del bloque de los filtros de los roles ═══ */

/* ═══ LAS FILAS DE LA TABLA DE ROLES, IGUALES (petición del usuario, 2026-10-15) ═════════════════════════════════════
   «Con Todos las filas no son homogéneas»: la del rol retirado se apaga con el color de lo secundario, pero con la MISMA
   letra y el mismo cuerpo que la vigente (antes el nombre llevaba `.bf-muted`, que además lo achicaba). Y la segunda
   acción —Retirar o Reactivar, según el estado— tiene un ancho fijo, para que «Editar» caiga en el mismo sitio en todas
   las filas en vez de bailar con la longitud del rótulo. */
.bf-htable .bf-table tr.off td:not(.acts),
.bf-htable .bf-table tr.off td:not(.acts) b {
  color: var(--bf-muted);
}

[data-live="productRoles"] .bf-table td.acts > form > .bf-btn {
  min-width: 7.25rem;
  justify-content: center;
}

/* ═══ fin del bloque de las filas de roles ═══ */

/* ═══ La portada de la instalación (Portal/PortadaPage.cs) ═══
   Una rejilla de paneles de widget de varios productos. Cada panel tiene su tamaño, sacado del `panelSize` de su
   producto: `sm` ocupa una celda, `md` dos de ancho y `lg` dos de ancho y dos de alto. El título de la página lo dice el rastro; el h1 queda para el lector de pantalla. */
.bf-portada-heading {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.bf-portada {
  container: bf-portada / inline-size;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(18rem, 100%), 1fr));
  grid-auto-rows: 22rem;
  grid-auto-flow: dense;
  gap: 1rem;
}

.bf-portada-panel {
  display: flex;
  flex-direction: column;
  min-width: 0;
  overflow: hidden;
  border-radius: var(--bf-radius-lg);
  background: var(--bf-card-veil);
  box-shadow: var(--bf-card-glass-drop), inset 0 0 0 1px var(--bf-card-glass-bevel);
}

/* Mobile-first (MF-2): uno debajo de otro. Los anchos dobles sólo cuando caben dos columnas, que depende del ancho
   de la rejilla y no de la ventana —con el lateral abierto la rejilla es más estrecha—, así que se pregunta al
   contenedor y no se añade un corte de @media (MF-3). 37rem son dos columnas de 18rem y el hueco. */
@container bf-portada (min-width: 37rem) {
  .bf-portada-panel[data-size="md"] { grid-column: span 2; }
  .bf-portada-panel[data-size="lg"] { grid-column: span 2; grid-row: span 2; }
}

.bf-portada-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.4rem 0.4rem 1rem;
  box-shadow: inset 0 -1px 0 var(--bf-card-glass-bevel);
}

.bf-portada-title {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.bf-portada-body {
  position: relative;
  flex: 1;
  min-height: 0;
}

.bf-portada-body iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
}

.bf-portada-status {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: 1.5rem;
  text-align: center;
  color: var(--bf-muted);
}

.bf-portada-status[hidden] { display: none; }
.bf-portada-status p { margin: 0; }

/* ═══ fin del bloque de la portada ═══ */
